diff --git a/atd-vzv/src/views/summary/CrashesByTimeOfDay.js b/atd-vzv/src/views/summary/CrashesByTimeOfDay.js index 5ec5fbf2f..c982e5ffc 100644 --- a/atd-vzv/src/views/summary/CrashesByTimeOfDay.js +++ b/atd-vzv/src/views/summary/CrashesByTimeOfDay.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useCallback, useState, useEffect } from "react"; import axios from "axios"; import moment from "moment"; import clonedeep from "lodash.clonedeep"; @@ -33,6 +33,72 @@ const hourBlockArray = [...Array(24).keys()].map((hour) => moment({ hour }).format("hhA") ); +/** + * Build an array of objs for each hour window that holds totals of each day of the week + * @returns {Array} Array of objs for each hour window that holds totals of each day of the week + */ +const buildDataArray = () => { + // This array holds weekday totals for each hour window within a day + // Reaviz Heatmap expects array of weekday total objs to be reversed in order + const hourWindowTotalsByDay = dayOfWeekArray + .map((day) => ({ key: day, data: null })) // Initialize totals as null to unweight 0 in viz + .reverse(); + + return hourBlockArray.map((hour) => ({ + key: hour, + data: clonedeep(hourWindowTotalsByDay), + })); +}; + +/** + * Calculate the figures to populate the heatmap cells + * @param {*} records - Array of crash records returned from the Socrata query + * @param {Object} crashType - Object containing query and name details (see CrashTypeSelector component) + * @returns + */ +const calculateHourBlockTotals = (records, crashType) => { + const dataArray = buildDataArray(); + + records.forEach((record) => { + const recordDateTime = moment(record.crash_date); + const recordHour = recordDateTime.format("hhA"); + const recordDay = recordDateTime.format("ddd"); + + const hourData = dataArray.find((hour) => hour.key === recordHour).data; + const dayToIncrement = hourData.find((day) => day.key === recordDay); + + switch (crashType.name) { + case "fatalities": + dayToIncrement.data += parseInt(record.death_cnt); + break; + case "seriousInjuries": + dayToIncrement.data += parseInt(record.sus_serious_injry_cnt); + break; + default: + dayToIncrement.data += + parseInt(record.death_cnt) + parseInt(record.sus_serious_injry_cnt); + break; + } + }); + + return dataArray; +}; + +/** + * Generate the query url for the Socrata query based on the active tab and crash type + * @param {Number} activeTab - The active tab index that corresponds to year option selected + * @param {Object} crashType - Object containing query and name details (see CrashTypeSelector component) + * @returns {String} The query url for the Socrata query + */ +const getFatalitiesByYearsAgoUrl = (activeTab, crashType) => { + const yearsAgoDate = moment().subtract(activeTab, "year").format("YYYY"); + let queryUrl = + activeTab === 0 + ? `${crashEndpointUrl}?$where=${crashType.queryStringCrash} AND crash_date between '${summaryCurrentYearStartDate}T00:00:00' and '${summaryCurrentYearEndDate}T23:59:59'` + : `${crashEndpointUrl}?$where=${crashType.queryStringCrash} AND crash_date between '${yearsAgoDate}-01-01T00:00:00' and '${yearsAgoDate}-12-31T23:59:59'`; + return queryUrl; +}; + const CrashesByTimeOfDay = () => { const [activeTab, setActiveTab] = useState(0); const [crashType, setCrashType] = useState([]); @@ -49,60 +115,8 @@ const CrashesByTimeOfDay = () => { useEffect(() => { if (!crashType.queryStringCrash) return; - const buildDataArray = () => { - // This array holds weekday totals for each hour window within a day - // Reaviz Heatmap expects array of weekday total objs to be reversed in order - const hourWindowTotalsByDay = dayOfWeekArray - .map((day) => ({ key: day, data: null })) // Initialize totals as null to unweight 0 in viz - .reverse(); - - // Return array of objs for each hour window that holds totals of each day of the week - return hourBlockArray.map((hour) => ({ - key: hour, - data: clonedeep(hourWindowTotalsByDay), - })); - }; - - const calculateHourBlockTotals = (records) => { - const dataArray = buildDataArray(); - - records.forEach((record) => { - const recordDateTime = moment(record.crash_date); - const recordHour = recordDateTime.format("hhA"); - const recordDay = recordDateTime.format("ddd"); - - const hourData = dataArray.find((hour) => hour.key === recordHour).data; - const dayToIncrement = hourData.find((day) => day.key === recordDay); - - switch (crashType.name) { - case "fatalities": - dayToIncrement.data += parseInt(record.death_cnt); - break; - case "seriousInjuries": - dayToIncrement.data += parseInt(record.sus_serious_injry_cnt); - break; - default: - dayToIncrement.data += - parseInt(record.death_cnt) + - parseInt(record.sus_serious_injry_cnt); - break; - } - }); - - return dataArray; - }; - - const getFatalitiesByYearsAgoUrl = () => { - const yearsAgoDate = moment().subtract(activeTab, "year").format("YYYY"); - let queryUrl = - activeTab === 0 - ? `${crashEndpointUrl}?$where=${crashType.queryStringCrash} AND crash_date between '${summaryCurrentYearStartDate}T00:00:00' and '${summaryCurrentYearEndDate}T23:59:59'` - : `${crashEndpointUrl}?$where=${crashType.queryStringCrash} AND crash_date between '${yearsAgoDate}-01-01T00:00:00' and '${yearsAgoDate}-12-31T23:59:59'`; - return queryUrl; - }; - - axios.get(getFatalitiesByYearsAgoUrl()).then((res) => { - const formattedData = calculateHourBlockTotals(res.data); + axios.get(getFatalitiesByYearsAgoUrl(activeTab, crashType)).then((res) => { + const formattedData = calculateHourBlockTotals(res.data, crashType); setHeatmapData(formattedData); }); }, [activeTab, crashType]); @@ -141,6 +155,8 @@ const CrashesByTimeOfDay = () => { const placeholderArray = heatmapData[0].data.map((data, i) => ({ key: dayOfWeekArray[i], data: maxForLegend[crashType.name], + // Add this metadata to find which cells to hide in the callback ref below + metadata: { isPlaceholder: true }, })); const placeholderObjForChartWeighting = { @@ -156,22 +172,14 @@ const CrashesByTimeOfDay = () => { setHeatmapDataWithPlaceholder(updatedWeightingData); }, [maxForLegend, heatmapData, crashType]); - // Hide placeholder cells - useEffect(() => { - const heatmapChildCellNumbers = [171, 172, 173, 174, 175, 176, 177]; - - let cellsToHide = heatmapChildCellNumbers.map((num) => - document.querySelector( - `#demographics-heatmap > div > svg > g > g:nth-child(${num})` - ) - ); - - cellsToHide.forEach((cell) => { - if (!!cell) { - cell.style.visibility = "hidden"; - } - }); - }, [heatmapDataWithPlaceholder, crashType, maxForLegend]); + // Hide placeholder cells with a callback ref + const heatmapCellRef = useCallback((node) => { + // Look for the isPlaceholder metadata that we placed there to identify the cells to hide + if (node?.props?.data?.metadata?.isPlaceholder) { + // Update the cell's style to hide it and its tooltip + node.rect.current.style.visibility = "hidden"; + } + }, []); const formatValue = (d) => { const value = d.data.value ? d.data.value : 0; @@ -199,7 +207,10 @@ const CrashesByTimeOfDay = () => { - + @@ -248,6 +259,8 @@ const CrashesByTimeOfDay = () => { emptyColor={colors.intensity1Of5Lowest} cell={