From 0d71166a97eec178b612e10c48e966c9fc3973bf Mon Sep 17 00:00:00 2001 From: Darren Boss Date: Fri, 19 Jul 2024 16:16:03 -0700 Subject: [PATCH 1/7] Fuel type distribution --- .vscode/settings.json | 1 + .../infoPanel/FireZoneUnitSummary.tsx | 6 +- .../fba/components/viz/CombustibleAreaViz.tsx | 7 +- .../fba/components/viz/ElevationInfoViz.tsx | 11 +- .../fba/components/viz/FuelDistribution.tsx | 20 +++ .../fba/components/viz/FuelSummary.tsx | 130 +++++++++++++++ .../fba/components/viz/FuelTypesBreakdown.tsx | 149 ------------------ .../components/viz/fuelDistribution.test.tsx | 28 ++++ .../components/forecastCell.test.tsx | 2 +- 9 files changed, 194 insertions(+), 160 deletions(-) create mode 100644 web/src/features/fba/components/viz/FuelDistribution.tsx create mode 100644 web/src/features/fba/components/viz/FuelSummary.tsx delete mode 100644 web/src/features/fba/components/viz/FuelTypesBreakdown.tsx create mode 100644 web/src/features/fba/components/viz/fuelDistribution.test.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 76f77ae55..ac35de154 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -73,6 +73,7 @@ "maxx", "maxy", "miny", + "luxon", "ndarray", "numba", "osgeo", diff --git a/web/src/features/fba/components/infoPanel/FireZoneUnitSummary.tsx b/web/src/features/fba/components/infoPanel/FireZoneUnitSummary.tsx index 070076028..a0b22f881 100644 --- a/web/src/features/fba/components/infoPanel/FireZoneUnitSummary.tsx +++ b/web/src/features/fba/components/infoPanel/FireZoneUnitSummary.tsx @@ -4,9 +4,9 @@ import { Grid } from '@mui/material' import { isUndefined } from 'lodash' import { ElevationInfoByThreshold, FireShape, FireShapeArea, FireZoneThresholdFuelTypeArea } from 'api/fbaAPI' import ElevationInfoViz from 'features/fba/components/viz/ElevationInfoViz' -import FuelTypesBreakdown from 'features/fba/components/viz/FuelTypesBreakdown' import InfoAccordion from 'features/fba/components/infoPanel/InfoAccordion' import { useTheme } from '@mui/material/styles' +import FuelSummary from 'features/fba/components/viz/FuelSummary' interface FireZoneUnitSummaryProps { selectedFireZoneUnit: FireShape | undefined @@ -40,8 +40,8 @@ const FireZoneUnitSummary = ({ fireZoneAreas={fireShapeAreas.filter(area => area.fire_shape_id == selectedFireZoneUnit?.fire_shape_id)} /> - - + + diff --git a/web/src/features/fba/components/viz/CombustibleAreaViz.tsx b/web/src/features/fba/components/viz/CombustibleAreaViz.tsx index d3f95b77a..4a509e310 100644 --- a/web/src/features/fba/components/viz/CombustibleAreaViz.tsx +++ b/web/src/features/fba/components/viz/CombustibleAreaViz.tsx @@ -8,9 +8,10 @@ const PREFIX = 'CombustibleAreaViz' const StyledTypography = styled(Typography, { name: `${PREFIX}-Typography` })({ - fontSize: '1.3rem', - textAlign: 'center', - variant: 'h3' + fontSize: '1rem', + fontWeight: 'bold', + paddingBottom: '0.5rem', + textAlign: 'left' }) export interface AdvisoryMetadataProps { diff --git a/web/src/features/fba/components/viz/ElevationInfoViz.tsx b/web/src/features/fba/components/viz/ElevationInfoViz.tsx index 65ca80f4a..977830ba7 100644 --- a/web/src/features/fba/components/viz/ElevationInfoViz.tsx +++ b/web/src/features/fba/components/viz/ElevationInfoViz.tsx @@ -19,9 +19,10 @@ const classes = { const Root = styled('div')({ [`& .${classes.header}`]: { - fontSize: '1.3rem', - textAlign: 'center', - variant: 'h3' + fontSize: '1rem', + fontWeight: 'bold', + paddingBottom: '0.5rem', + textAlign: 'center' }, [`& .${classes.wrapper}`]: { padding: '20px 10px' @@ -62,7 +63,9 @@ const ElevationInfoViz = (props: Props) => { return (
- HFI By Elevation + + HFI By Elevation + diff --git a/web/src/features/fba/components/viz/FuelDistribution.tsx b/web/src/features/fba/components/viz/FuelDistribution.tsx new file mode 100644 index 000000000..84b67e26b --- /dev/null +++ b/web/src/features/fba/components/viz/FuelDistribution.tsx @@ -0,0 +1,20 @@ +import { Box } from '@mui/material' +import React from 'react' +import { getColorByFuelTypeCode } from 'features/fba/components/viz/color' + +interface FuelDistributionProps { + code: string + percent: number +} + +// Represents the percent contribution of the given fuel type to the overall high HFI area. +const FuelDistribution = ({ code, percent }: FuelDistributionProps) => { + return ( + + ) +} + +export default React.memo(FuelDistribution) diff --git a/web/src/features/fba/components/viz/FuelSummary.tsx b/web/src/features/fba/components/viz/FuelSummary.tsx new file mode 100644 index 000000000..890e6e4bc --- /dev/null +++ b/web/src/features/fba/components/viz/FuelSummary.tsx @@ -0,0 +1,130 @@ +import React, { useEffect, useState } from 'react' +import { FireShape, FireZoneThresholdFuelTypeArea } from 'api/fbaAPI' +import { Box, Tooltip, Typography } from '@mui/material' +import { groupBy, isUndefined } from 'lodash' +import { DateTime } from 'luxon' +import FuelDistribution from 'features/fba/components/viz/FuelDistribution' +import { DataGridPro, GridColDef, GridRenderCellParams } from '@mui/x-data-grid-pro' +import { useTheme } from '@mui/material/styles' + +export interface FuelTypeInfoSummary { + area: number + criticalHoursStart?: DateTime + criticalHoursStop?: DateTime + id: number + code: string + description: string + percent?: number + selected: boolean +} + +interface FuelSummaryProps { + fuelTypeInfo: Record + selectedFireZoneUnit: FireShape | undefined +} + +const columns: GridColDef[] = [ + { + field: 'code', + headerName: 'Fuel', + sortable: false, + width: 75, + renderCell: (params: GridRenderCellParams) => ( + + {params.row[params.field]} + + ) + }, + { + field: 'area', + flex: 3, + headerName: 'Percent of High HFI Area', + minWidth: 200, + sortable: false, + renderCell: (params: GridRenderCellParams) => { + console.log(params) + return + } + } + // { field: 'hours', flex: 2, headerName: 'Critical Hours', minWidth: 150, sortable: false } +] + +const FuelSummary = ({ fuelTypeInfo, selectedFireZoneUnit }: FuelSummaryProps) => { + const theme = useTheme() + const [fuelTypeInfoRollup, setFuelTypeInfoRollup] = useState([]) + + useEffect(() => { + if (isUndefined(fuelTypeInfo) || isUndefined(selectedFireZoneUnit)) { + setFuelTypeInfoRollup([]) + return + } + const shapeId = selectedFireZoneUnit.fire_shape_id + const fuelDetails = fuelTypeInfo[shapeId] + if (isUndefined(fuelDetails)) { + setFuelTypeInfoRollup([]) + return + } + // Sum the total area with HFI > 4000 for all fuel types + const totalHFIArea4K = fuelDetails.reduce((acc, { area }) => acc + area, 0) + const rollUp: FuelTypeInfoSummary[] = [] + // We receive HFI area per fuel type per HFI threshold (4-10K and >10K), so group fuel type. + // Iterate through the groups adding the area for both HFI thresholds' we're interested in all + // HFI > 4,000. + const groupedFuelDetails = groupBy(fuelDetails, 'fuel_type.fuel_type_id') + for (const key in groupedFuelDetails) { + const groupedFuelDetail = groupedFuelDetails[key] + if (groupedFuelDetail.length) { + const area = groupedFuelDetail.reduce((acc, { area }) => acc + area, 0) + const fuelType = groupedFuelDetail[0].fuel_type + const fuelInfo: FuelTypeInfoSummary = { + area, + code: fuelType.fuel_type_code, + description: fuelType.description, + id: fuelType.fuel_type_id, + percent: totalHFIArea4K ? (area / totalHFIArea4K) * 100 : 0, + selected: false + } + rollUp.push(fuelInfo) + } + } + setFuelTypeInfoRollup(rollUp) + }, [fuelTypeInfo]) // eslint-disable-line react-hooks/exhaustive-deps + + return ( + + + HFI Distribution by Fuel Type + + {fuelTypeInfoRollup.length === 0 ? ( + No fuel type information available. + ) : ( + + )} + + ) +} + +export default FuelSummary diff --git a/web/src/features/fba/components/viz/FuelTypesBreakdown.tsx b/web/src/features/fba/components/viz/FuelTypesBreakdown.tsx deleted file mode 100644 index edbd1c414..000000000 --- a/web/src/features/fba/components/viz/FuelTypesBreakdown.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import React from 'react' -import { styled } from '@mui/material/styles' -import { Typography } from '@mui/material' -import { isUndefined } from 'lodash' -import { FireShape, FireZoneThresholdFuelTypeArea } from 'api/fbaAPI' -import { PieChart, Pie, ResponsiveContainer, Cell } from 'recharts' -import { getColorByFuelTypeCode } from 'features/fba/components/viz/color' - -const PREFIX = 'FuelTypesBreakdown' - -const FuelTypesHeader = styled(Typography, { - name: `${PREFIX}-fuelTypesHeader` -})({ - fontSize: '1.3rem', - textAlign: 'center', - variant: 'h3' -}) - -const PieChartHeader = styled(Typography, { - name: `${PREFIX}-pieChartHeader` -})({ - fontSize: '1rem', - textAlign: 'center', - variant: 'h4' -}) - -interface Props { - className?: string - selectedFireZone: FireShape | undefined - fuelTypeInfo: Record -} - -interface FuelTypeDataForPieChart { - area: number - fuel_type_code: string -} - -const RADIAN = Math.PI / 180 - -const FuelTypesBreakdown = (props: Props) => { - const renderCustomizedLabel = ({ - cx, - cy, - midAngle, - outerRadius, - percent, - fuel_type_code - }: { - cx: number - cy: number - midAngle: number - outerRadius: number - percent: number - fuel_type_code: string - }) => { - // Labels are positioned at the outer edge of the pie + the length of label lines (20px) + - // an arbitrary buffer/whitespace of 5px - const labelRadius = outerRadius + 25 - const x = cx + labelRadius * Math.cos(-midAngle * RADIAN) - const y = cy + labelRadius * Math.sin(-midAngle * RADIAN) - - // Only label pie slices that contribute >= 2% - if (percent * 100 < 2) { - return - } - - return ( - cx ? 'start' : 'end'}> - {`${fuel_type_code} (${(percent * 100).toFixed(0)}%)`} - - ) - } - - const renderLabelLine = ({ - percent, - points, - stroke - }: { - percent: number - points: { x: number; y: number }[] - stroke: string - }) => { - if (!points || points.length < 2 || percent * 100 < 2) { - return <> - } - - return - } - - if (isUndefined(props.selectedFireZone) || isUndefined(props.fuelTypeInfo[props.selectedFireZone.fire_shape_id])) { - return
- } else { - const advisories: FuelTypeDataForPieChart[] = [] - const warnings: FuelTypeDataForPieChart[] = [] - props.fuelTypeInfo[props.selectedFireZone?.fire_shape_id].forEach(record => { - if (record.threshold.id === 1) { - advisories.push({ area: record.area, fuel_type_code: record.fuel_type.fuel_type_code }) - } else if (record.threshold.id === 2) { - warnings.push({ area: record.area, fuel_type_code: record.fuel_type.fuel_type_code }) - } - }) - return ( -
- HFI by Fuel Type - Advisories (HFI: 4,000-10,000 kW/m) - - - - {advisories.map(entry => ( - - ))} - - - - Warnings (HFI: +10,000 kW/m) - - - - {warnings.map(entry => ( - - ))} - - - -
- ) - } -} - -export default React.memo(FuelTypesBreakdown) diff --git a/web/src/features/fba/components/viz/fuelDistribution.test.tsx b/web/src/features/fba/components/viz/fuelDistribution.test.tsx new file mode 100644 index 000000000..9217fe146 --- /dev/null +++ b/web/src/features/fba/components/viz/fuelDistribution.test.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import { render } from '@testing-library/react' +import FuelDistribution from 'features/fba/components/viz/FuelDistribution' + +describe('FuelDistribution', () => { + it('should have width relative to parent', () => { + const rendered = render( +
+ +
+ ) + + const element = rendered.getByTestId('fuel-distribution-box') + expect(element).toBeInTheDocument() + expect(element).toHaveStyle('width: 50%') + }) + it('should have the correct background color', () => { + const rendered = render( +
+ +
+ ) + + const element = rendered.getByTestId('fuel-distribution-box') + expect(element).toBeInTheDocument() + expect(element).toHaveStyle('background: #226633') + }) +}) diff --git a/web/src/features/moreCast2/components/forecastCell.test.tsx b/web/src/features/moreCast2/components/forecastCell.test.tsx index c4dd780c4..6862f69f6 100644 --- a/web/src/features/moreCast2/components/forecastCell.test.tsx +++ b/web/src/features/moreCast2/components/forecastCell.test.tsx @@ -54,7 +54,7 @@ describe('ForecastCell', () => { expect(element).toBeInTheDocument() }) it('should throw an error when showGreaterThan and showLessThan are both positive', () => { - // Suppres the console error message for an unhandled error + // Suppress the console error message for an unhandled error const consoleErrorFn = jest.spyOn(console, 'error').mockImplementation(() => jest.fn()) expect(() => { render( From 6637c1b3190a85519b616398fdc763977116fcd3 Mon Sep 17 00:00:00 2001 From: Darren Boss Date: Mon, 22 Jul 2024 11:00:07 -0700 Subject: [PATCH 2/7] Column header --- web/src/features/fba/components/viz/FuelSummary.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/features/fba/components/viz/FuelSummary.tsx b/web/src/features/fba/components/viz/FuelSummary.tsx index 890e6e4bc..ed32c64c5 100644 --- a/web/src/features/fba/components/viz/FuelSummary.tsx +++ b/web/src/features/fba/components/viz/FuelSummary.tsx @@ -38,7 +38,7 @@ const columns: GridColDef[] = [ { field: 'area', flex: 3, - headerName: 'Percent of High HFI Area', + headerName: 'Area Distribution', minWidth: 200, sortable: false, renderCell: (params: GridRenderCellParams) => { From 0902daa16db9ab0a8b2a1874634a58f184df559d Mon Sep 17 00:00:00 2001 From: Darren Boss Date: Mon, 22 Jul 2024 13:45:32 -0700 Subject: [PATCH 3/7] titles --- web/src/features/fba/components/viz/FuelSummary.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/features/fba/components/viz/FuelSummary.tsx b/web/src/features/fba/components/viz/FuelSummary.tsx index ed32c64c5..0e694e6ed 100644 --- a/web/src/features/fba/components/viz/FuelSummary.tsx +++ b/web/src/features/fba/components/viz/FuelSummary.tsx @@ -26,7 +26,7 @@ interface FuelSummaryProps { const columns: GridColDef[] = [ { field: 'code', - headerName: 'Fuel', + headerName: 'Fuel Type', sortable: false, width: 75, renderCell: (params: GridRenderCellParams) => ( @@ -38,7 +38,7 @@ const columns: GridColDef[] = [ { field: 'area', flex: 3, - headerName: 'Area Distribution', + headerName: 'Distribution > 4k kW/m', minWidth: 200, sortable: false, renderCell: (params: GridRenderCellParams) => { From 2025fad89a3a04d99b7c15d2956bb9b31b947584 Mon Sep 17 00:00:00 2001 From: Darren Boss Date: Mon, 22 Jul 2024 13:50:10 -0700 Subject: [PATCH 4/7] PR feedback and a comment --- web/src/features/fba/components/viz/FuelSummary.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/features/fba/components/viz/FuelSummary.tsx b/web/src/features/fba/components/viz/FuelSummary.tsx index 0e694e6ed..4a8fada58 100644 --- a/web/src/features/fba/components/viz/FuelSummary.tsx +++ b/web/src/features/fba/components/viz/FuelSummary.tsx @@ -10,7 +10,7 @@ import { useTheme } from '@mui/material/styles' export interface FuelTypeInfoSummary { area: number criticalHoursStart?: DateTime - criticalHoursStop?: DateTime + criticalHoursEnd?: DateTime id: number code: string description: string @@ -23,6 +23,7 @@ interface FuelSummaryProps { selectedFireZoneUnit: FireShape | undefined } +// Column definitions for fire zone unit fuel summary table const columns: GridColDef[] = [ { field: 'code', @@ -46,7 +47,6 @@ const columns: GridColDef[] = [ return } } - // { field: 'hours', flex: 2, headerName: 'Critical Hours', minWidth: 150, sortable: false } ] const FuelSummary = ({ fuelTypeInfo, selectedFireZoneUnit }: FuelSummaryProps) => { From c06a9100674558a3c0586a75ed23d6f2f652cb33 Mon Sep 17 00:00:00 2001 From: Darren Boss Date: Mon, 22 Jul 2024 14:07:24 -0700 Subject: [PATCH 5/7] Remove unnecessary styled div --- .../fba/components/viz/ElevationInfoViz.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/web/src/features/fba/components/viz/ElevationInfoViz.tsx b/web/src/features/fba/components/viz/ElevationInfoViz.tsx index 977830ba7..40a1b7749 100644 --- a/web/src/features/fba/components/viz/ElevationInfoViz.tsx +++ b/web/src/features/fba/components/viz/ElevationInfoViz.tsx @@ -17,18 +17,6 @@ const classes = { wrapper: `${PREFIX}-wrapper` } -const Root = styled('div')({ - [`& .${classes.header}`]: { - fontSize: '1rem', - fontWeight: 'bold', - paddingBottom: '0.5rem', - textAlign: 'center' - }, - [`& .${classes.wrapper}`]: { - padding: '20px 10px' - } -}) - interface Props { className?: string selectedFireZone: FireShape | undefined @@ -37,7 +25,7 @@ interface Props { const ElevationInfoViz = (props: Props) => { if (isUndefined(props.hfiElevationInfo) || props.hfiElevationInfo.length === 0) { - return + return } const advisoryElevationInfoByThreshold = props.hfiElevationInfo.filter(info => info.threshold === 1) const warnElevationInfoByThreshold = props.hfiElevationInfo.filter(info => info.threshold === 2) From ad8aa5d5e305bbdf5b61bacbb3c5e52ea781469f Mon Sep 17 00:00:00 2001 From: Darren Boss Date: Mon, 22 Jul 2024 14:29:57 -0700 Subject: [PATCH 6/7] Add header background --- web/src/features/fba/components/viz/FuelSummary.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/src/features/fba/components/viz/FuelSummary.tsx b/web/src/features/fba/components/viz/FuelSummary.tsx index 4a8fada58..0a3e39c6c 100644 --- a/web/src/features/fba/components/viz/FuelSummary.tsx +++ b/web/src/features/fba/components/viz/FuelSummary.tsx @@ -27,6 +27,7 @@ interface FuelSummaryProps { const columns: GridColDef[] = [ { field: 'code', + headerClassName: 'fuel-summary-header', headerName: 'Fuel Type', sortable: false, width: 75, @@ -39,6 +40,7 @@ const columns: GridColDef[] = [ { field: 'area', flex: 3, + headerClassName: 'fuel-summary-header', headerName: 'Distribution > 4k kW/m', minWidth: 200, sortable: false, @@ -119,6 +121,9 @@ const FuelSummary = ({ fuelTypeInfo, selectedFireZoneUnit }: FuelSummaryProps) = overflow: 'hidden', '& .MuiDataGrid-columnHeaderTitle': { fontWeight: 'bold' + }, + '& .fuel-summary-header': { + background: '#F1F1F1' } }} > From 92775cd66dd9414324ab1ce12420a896d55042e3 Mon Sep 17 00:00:00 2001 From: dgboss Date: Mon, 22 Jul 2024 15:42:42 -0700 Subject: [PATCH 7/7] Update web/src/features/fba/components/viz/FuelSummary.tsx --- web/src/features/fba/components/viz/FuelSummary.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/features/fba/components/viz/FuelSummary.tsx b/web/src/features/fba/components/viz/FuelSummary.tsx index 0a3e39c6c..1a7b3ed52 100644 --- a/web/src/features/fba/components/viz/FuelSummary.tsx +++ b/web/src/features/fba/components/viz/FuelSummary.tsx @@ -45,7 +45,6 @@ const columns: GridColDef[] = [ minWidth: 200, sortable: false, renderCell: (params: GridRenderCellParams) => { - console.log(params) return } }