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..40a1b7749 100644 --- a/web/src/features/fba/components/viz/ElevationInfoViz.tsx +++ b/web/src/features/fba/components/viz/ElevationInfoViz.tsx @@ -17,17 +17,6 @@ const classes = { wrapper: `${PREFIX}-wrapper` } -const Root = styled('div')({ - [`& .${classes.header}`]: { - fontSize: '1.3rem', - textAlign: 'center', - variant: 'h3' - }, - [`& .${classes.wrapper}`]: { - padding: '20px 10px' - } -}) - interface Props { className?: string selectedFireZone: FireShape | undefined @@ -36,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) @@ -62,7 +51,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..1a7b3ed52 --- /dev/null +++ b/web/src/features/fba/components/viz/FuelSummary.tsx @@ -0,0 +1,134 @@ +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 + criticalHoursEnd?: DateTime + id: number + code: string + description: string + percent?: number + selected: boolean +} + +interface FuelSummaryProps { + fuelTypeInfo: Record + selectedFireZoneUnit: FireShape | undefined +} + +// Column definitions for fire zone unit fuel summary table +const columns: GridColDef[] = [ + { + field: 'code', + headerClassName: 'fuel-summary-header', + headerName: 'Fuel Type', + sortable: false, + width: 75, + renderCell: (params: GridRenderCellParams) => ( + + {params.row[params.field]} + + ) + }, + { + field: 'area', + flex: 3, + headerClassName: 'fuel-summary-header', + headerName: 'Distribution > 4k kW/m', + minWidth: 200, + sortable: false, + renderCell: (params: GridRenderCellParams) => { + return + } + } +] + +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(