1HotspotGraph = function(container) { 2 3 let self = this; 4 5 let hotspotModule = document.querySelector(container); 6 7 let hotspotContainer = hotspotModule.querySelector("#hotspot-container"), 8 currentTime = hotspotModule.querySelector("#current-time"), 9 leftArrow = hotspotModule.querySelector("#left-arrow"), 10 rightArrow = hotspotModule.querySelector("#right-arrow"); 11 12 // get the radios/labels for the modes of the hotspot module 13 let weekRadio = hotspotModule.querySelector("#week"), 14 monthRadio = hotspotModule.querySelector("#month"), 15 yearRadio = hotspotModule.querySelector("#year"); 16 17 let weekLabel = hotspotModule.querySelector("#week-label"), 18 monthLabel = hotspotModule.querySelector("#month-label"), 19 yearLabel = hotspotModule.querySelector("#year-label"); 20 21 let menuRadios = [weekRadio, monthRadio, yearRadio]; 22 let menuLabels = [weekLabel, monthLabel, yearLabel]; 23 let menuNames = ["week", "month", "year"]; 24 let hotspot_graph_mode_index = 1; 25 26 let FIRST_DATE_WITH_DATA = null; // will be set to the first date with data 27 let LAST_DATE_WITH_DATA = null; // will be set to the last date with data 28 29 // set up the radios for switching the mode of the hotspot graph 30 menuRadios.forEach(function (radio, i) { 31 radio.addEventListener("change", function () { 32 menuLabels[hotspot_graph_mode_index].classList.remove("active"); 33 hotspot_graph_mode_index = i; 34 menuLabels[hotspot_graph_mode_index].classList.add("active"); 35 self.resetDatesBasedOnMode(getAllMapFeatures(), menuNames[hotspot_graph_mode_index]); 36 self.update(getAllMapFeatures(), new Date(currentTime.getAttribute("data-start")), new Date(currentTime.getAttribute("data-end")), menuNames[hotspot_graph_mode_index]); 37 }); 38 }); 39 40 // sets start and end dates for when the mode of the graph is switched 41 // i.e. will calculate the proper start and end dates for a month when 42 // the start and end dates used to be a week long 43 self.resetDatesBasedOnMode = function(data, mode) { 44 let startDate = new Date(currentTime.getAttribute("data-start")); 45 let endDate = new Date(currentTime.getAttribute("data-end")); 46 47 let newStartDate = null; 48 let newEndDate = null; 49 50 if(mode == "week") { 51 newStartDate = startDate; //start at the same start date 52 newEndDate = new Date(startDate.getTime() + 1000 * 60 * 60 * 24 * 6); // add 6 days to get week 53 } else if (mode == "month") { 54 newStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), 1); //get first day of current month 55 newEndDate = new Date(startDate.getFullYear(), startDate.getMonth()+1, 0); //get the last day of current month 56 } else if (mode == "year") { 57 newStartDate = new Date(startDate.getFullYear(), 0, 1); //get first day of current year 58 newEndDate = new Date(startDate.getFullYear()+1, 0, 0); //get the last day of current year 59 } 60 61 62 // if the new date range will be out of the range of the data, reinitialize the dates 63 if(newEndDate < FIRST_DATE_WITH_DATA || newStartDate > LAST_DATE_WITH_DATA) { 64 if(mode == "week") { 65 newStartDate = FIRST_DATE_WITH_DATA; //start at first date with data 66 newEndDate = new Date(newStartDate.getTime() + 1000 * 60 * 60 * 24 * 6); // plus 6 days 67 } else if (mode == "month") { 68 newStartDate = new Date(FIRST_DATE_WITH_DATA.getFullYear(), FIRST_DATE_WITH_DATA.getMonth(), 1); //get the first day of the month 69 newEndDate = new Date(FIRST_DATE_WITH_DATA.getFullYear(), FIRST_DATE_WITH_DATA.getMonth()+1, 0); //get the last day of the month 70 } else if (mode == "year") { 71 newStartDate = new Date(FIRST_DATE_WITH_DATA.getFullYear(), 0, 1); //get the first day of the year 72 newEndDate = new Date(FIRST_DATE_WITH_DATA.getFullYear()+1, 0, 0); //get the last day of the year 73 } 74 } 75 76 // set the dates in the attributes of the page 77 currentTime.setAttribute("data-start", newStartDate); 78 currentTime.setAttribute("data-end", newEndDate); 79 } 80 81 82 leftArrow.addEventListener("click", function () { 83 let mode = menuNames[hotspot_graph_mode_index]; 84 // get current start date 85 currentStartDate = new Date(currentTime.getAttribute("data-start")); 86 87 // compute new date range based on mode 88 if(mode == "week") { 89 newEndDate = new Date(currentStartDate.getTime() - 1000 * 60 * 60 * 24); // minus 1 day from current start 90 newStartDate = new Date(newEndDate.getTime() - 1000 * 60 * 60 * 24 * 6); // minus 6 days 91 } else if (mode == "month") { 92 newStartDate = new Date(currentStartDate.getFullYear(), currentStartDate.getMonth()-1, 1); //get the first day of last month 93 newEndDate = new Date(currentStartDate.getFullYear(), currentStartDate.getMonth(), 0); //get the last day of the last month 94 } else if (mode == "year") { 95 newStartDate = new Date(currentStartDate.getFullYear()-1, 0, 1); //get the first day of the last year 96 newEndDate = new Date(currentStartDate.getFullYear(), 0, 0); //get the last day of the last year 97 } 98 // pass new date range to generateHotspotGraph 99 self.update(getAllMapFeatures(), newStartDate, newEndDate, menuNames[hotspot_graph_mode_index]); 100 }); 101 102 rightArrow.addEventListener("click", function () { 103 let mode = menuNames[hotspot_graph_mode_index]; 104 // get current end date 105 currentEndDate = new Date(currentTime.getAttribute("data-end")); 106 107 // compute new date range based on mode 108 if(mode == "week") { 109 newStartDate = new Date(currentEndDate.getTime()+ 1000 * 60 * 60 * 24 * 1); // start at old last day + 1 day 110 newEndDate = new Date(newStartDate.getTime() + 1000 * 60 * 60 * 24 * 6); // add 6 days 111 } else if (mode == "month") { 112 newStartDate = new Date(currentEndDate.getFullYear(), currentEndDate.getMonth()+1, 1); //get first day of the next month 113 newEndDate = new Date(currentEndDate.getFullYear(), currentEndDate.getMonth()+2, 0); //get the last day of the next month 114 } else if (mode == "year") { 115 newStartDate = new Date(currentEndDate.getFullYear()+1, 0, 1); //get first day of the next year 116 newEndDate = new Date(currentEndDate.getFullYear()+2, 0, 0
116); //get the last day of the next year 117 } 118 119 // pass new date range to generateHotspotGraph 120 self.update(getAllMapFeatures(), newStartDate, newEndDate, menuNames[hotspot_graph_mode_index]); 121 }); 122 123 self.generateHotspotData = function(allData) { 124 // temporary proxy for hotspots: filter data points with PM 2.5 <= 40 125 let highPmData = allData.filter((datum) => datum.properties["pm25"] > 40); 126 127 // construct an array of arrays to be used for hotspot line graph 128 // 1st value of each child array is date, 2nd value is # of hotspots for that date 129 let dates = highPmData.map((datum) => datum.properties["time"]); 130 let minDate = d3.min(dates); 131 let maxDate = d3.max(dates); 132 let dateObjToString = d3.timeFormat("%Y-%m-%d"); 133 134 135 // initialize global data about first date with data and last date with data 136 if(FIRST_DATE_WITH_DATA == null) { 137 FIRST_DATE_WITH_DATA = minDate; 138 } 139 140 if(LAST_DATE_WITH_DATA == null) { 141 LAST_DATE_WITH_DATA = maxDate; 142 } 143 144 let hotspotData = {}; 145 dates.forEach(function (dateObj) { 146 let date = dateObjToString(dateObj); 147 if (hotspotData[date]) { 148 hotspotData[date] += 1; 149 return; 150 } 151 hotspotData[date] = 1; 152 }); 153 154 return hotspotData; 155 } 156 157 self.update = function(data, startDate, endDate, mode = "month") { 158 let hotspotData = self.generateHotspotData(data); 159 160 // if startDate and endDate args not provided, determine it from the data 161 // generally, args would be provided when *updating* the graph, but not for initial load 162 if (typeof startDate === "undefined") { 163 startDate = new Date(FIRST_DATE_WITH_DATA.getFullYear(), FIRST_DATE_WITH_DATA.getMonth(), 1); //get the first day of the month 164 } 165 if (typeof endDate === "undefined") { 166 endDate = new Date(FIRST_DATE_WITH_DATA.getFullYear(), FIRST_DATE_WITH_DATA.getMonth()+1, 0); //get the last day of the month 167 } 168 169 // only keep values for the one time range specified 170 filteredHotspotData = Object.entries(hotspotData); // turn into array 171 filteredHotspotData = filteredHotspotData.filter( 172 (datum) => new Date(datum[0]) >= startDate && new Date(datum[0]) <= endDate 173 ); 174 175 // remove previous graph if one exists 176 d3.select(hotspotContainer).selectAll("svg").remove(); 177 // remove previous "no data" notice if it exists 178 d3.select(hotspotContainer).selectAll("p").remove(); 179 180 // set up SVG 181 let width = hotspotContainer.offsetWidth, 182 height = 200, 183 MARGINS = {left: 50, right: 0}; 184 let svg = d3 185 .select(hotspotContainer) 186 .append("svg") 187 .attr("viewBox", `0 0 ${width} ${height}`); 188 189 // x scale function 190 // initialize x-axis as showing the time period, starting from the earliest date in the data 191 var x = d3 192 .scaleTime() 193 .domain([startDate, endDate]) 194 .range([MARGINS.left, width - MARGINS.right - MARGINS.left]); 195 196 // y scale function 197 hotspotYValues = Array.from(filteredHotspotData, x => x[1]); 198 var y = d3 199 .scaleLinear() 200 .domain([0, d3.max(hotspotYValues) + 10]) 201 .range([height, 0]); 202 203 // axes 204 let xAxis = null; 205 206 // set x axis tick numbers and labels based on the mode of the hotspot graph 207 if(mode == "week") { 208 xAxis = (g) => 209 g.attr("transform", `translate(0, ${height})`).call( 210 d3 211 .axisBottom(x) 212 .ticks(7) 213 .tickFormat((date) => date.getDate()) // labeled by day of month 214 ); 215 } else if (mode == "month") { 216 xAxis = (g) => 217 g.attr("transform", `translate(0, ${height})`).call( 218 d3 219 .axisBottom(x) 220 .ticks() 221 .tickFormat((date) => date.getDate()) // labeled by day of month 222 ); 223 } else if (mode == "year") { 224 xAxis = (g) => 225 g.attr("transform", `translate(0, ${height})`).call( 226 d3 227 .axisBottom(x) 228 .ticks(12) 229 .tickFormat((date) => date.getMonth()+1) // labeled by numbered month 230 ); 231 } 232 svg.append("g").call(xAxis); 233 234 let yAxis = (g) => g.call(d3.axisLeft(y).ticks(4)); 235 svg.append("g").call(yAxis) 236 .attr("transform", "translate(" + (MARGINS.left) + ",0)") 237 238 239 svg.append("text") 240 .attr("transform", "rotate(-90)") 241 .attr("y", 0) 242 .attr("x",0 - (height / 2)) 243 .attr("dy", "1em") 244 .attr("fill", "white") 245 .style("text-anchor", "middle") 246 .text("Number of Hotspots"); 247 248 // line graph 249 let lineGenerator = d3 250 .line() 251 .defined((d) => d !== null) // leave gap in line for missing data 252 .x((d) => x(new Date(d[0]))) // date 253 .y((d) => y(d[1])); // number of hotspots for that date 254 svg 255 .append("path") 256 .datum(filteredHotspotData) // turn object into array 257 .attr("class", "line") // assign a class for styling 258 .attr("stroke", "#7d919e") 259 .attr("d", lineGenerator); // calls the line generator 260 261 // draw circles as well 262 svg 263 .selectAll("circle") 264 .data(filteredHotspotData) 265 .enter() 266 .append("circle") 267 .attr("cx", (d) => x(new Date(d[0]))) 268 .attr("cy", (d) => y(d[1])) 269 .attr("fill", "#7d919e") // gray color 270 .attr("r", 2); 271 272 // if no hotspot data, show message on graph 273 if (hotspotContainer.querySelector(".line").getAttribute("d") == null) { 274 let p = document.createElement("p"); 275 p.setAttribute("class", "notice"); 276 let notice = document.createTextNode( 277 "No hotspot data for this time period" 278 ); 279 p.appendChild(notice); 280 hotspotContainer.appendChild(p); 281 } 282 283 // populate current time in the temporal navigation below the graph 284 populateCurrentTime(startDate, endDate, mode); 285 } 286 287 // adds/updates the string in the hotspot panel time navigation 288 // currently only implemented for weekly time periods 289 function populateCurrentTime(startDate, endDate, mode) { 290 291 // populate attributes so other graph re-drawing processes can pull from this 292 currentTime.setAttribute("data-start", startDate); 293 currentTime.setAttribute("data-end", endDate); 294 295 if(mode == "week") {
296 startMonth = startDate.toLocaleString("default", { month: "long" }); 297 endMonth = endDate.toLocaleString("default", { month: "long" }); 298 299 let year = startDate.toLocaleString("default", { year: "numeric" }); 300 if (startMonth == endMonth) { 301 // e.g. "March 5â11 2020" 302 currentTime.innerText = `${startMonth} ${startDate.getDate()} â ${endDate.getDate()} ${year}`; 303 return; 304 } 305 // e.g. "March 29 2020 â April 4 2020" 306 currentTime.innerText = `${startMonth} ${startDate.getDate()} ${year} â${endMonth} ${endDate.getDate()} ${year}`; 307 } else if (mode == "month"){ 308 let year = startDate.toLocaleString("default", { year: "numeric" }); 309 let month = startDate.toLocaleString("default", { month: "long" }); 310 currentTime.innerText = `${month} ${year}`; // display the month in view 311 } else if (mode == "year"){ 312 let year = startDate.toLocaleString("default", { year: "numeric" }); 313 currentTime.innerText = `${year}`; //display the year in view 314 } 315 } 316 317 self.getCurrentTime = function() { 318 return currentTime; 319 } 320}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.