From 9e8130fe2207d85453421babad8a5d5dac2aa55c Mon Sep 17 00:00:00 2001 From: Viktor Andersson <30777521+VIKTORVAV99@users.noreply.github.com> Date: Fri, 3 Jan 2025 22:53:46 +0100 Subject: [PATCH] perf: Memoize BarBreakdownChart.tsx (#7674) * perf: Memoize BarBreakdownChart.tsx * simplify EmptyBarBreakdownChart.tsx --- .../bar-breakdown/BarBreakdownChart.tsx | 59 ++++++++++--------- .../BarElectricityBreakdownChart.tsx | 6 +- .../BarElectricityProductionChart.tsx | 5 +- .../bar-breakdown/EmptyBarBreakdownChart.tsx | 46 ++++++--------- .../charts/bar-breakdown/utils.test.ts | 12 ++-- .../features/charts/bar-breakdown/utils.ts | 29 ++++++--- .../useBarElectricityBreakdownChartData.ts | 21 ++++--- .../charts/hooks/useOriginChartData.ts | 2 +- 8 files changed, 96 insertions(+), 84 deletions(-) diff --git a/web/src/features/charts/bar-breakdown/BarBreakdownChart.tsx b/web/src/features/charts/bar-breakdown/BarBreakdownChart.tsx index 08f60d76d9..abf6aefb17 100644 --- a/web/src/features/charts/bar-breakdown/BarBreakdownChart.tsx +++ b/web/src/features/charts/bar-breakdown/BarBreakdownChart.tsx @@ -6,7 +6,7 @@ import { useHeaderHeight } from 'hooks/headerHeight'; import { TFunction } from 'i18next'; import { useAtomValue } from 'jotai'; import { CircleDashed, TrendingUpDown, X } from 'lucide-react'; -import React, { useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ElectricityModeType, ZoneKey } from 'types'; import useResizeObserver from 'use-resize-observer'; @@ -76,49 +76,50 @@ function BarBreakdownChart({ currentZoneDetail?.estimatedPercentage ); + const onMouseOver = useCallback( + (layerKey: ElectricityModeType | ZoneKey, event: React.MouseEvent) => { + const { clientX, clientY } = event; + + const position = getOffsetTooltipPosition({ + mousePositionX: clientX || 0, + mousePositionY: clientY || 0, + tooltipHeight: displayByEmissions ? 190 : 360, + isBiggerThanMobile, + }); + + setTooltipData({ + selectedLayerKey: layerKey, + x: position.x, + y: position.y, + }); + }, + [displayByEmissions, isBiggerThanMobile] + ); + + const onMouseOut = useCallback(() => { + if (!isMobile) { + setTooltipData(null); + } + }, [isMobile]); + if (isLoading) { return null; } if (!currentZoneDetail) { return ( -
+ -
+ ); } - const onMouseOver = ( - layerKey: ElectricityModeType | ZoneKey, - event: React.MouseEvent - ) => { - const { clientX, clientY } = event; - - const position = getOffsetTooltipPosition({ - mousePositionX: clientX || 0, - mousePositionY: clientY || 0, - tooltipHeight: displayByEmissions ? 190 : 360, - isBiggerThanMobile, - }); - - setTooltipData({ - selectedLayerKey: layerKey, - x: position.x, - y: position.y, - }); - }; - - const onMouseOut = () => { - if (!isMobile) { - setTooltipData(null); - } - }; - return ( ); } + +export default memo(BarElectricityProductionChart); diff --git a/web/src/features/charts/bar-breakdown/EmptyBarBreakdownChart.tsx b/web/src/features/charts/bar-breakdown/EmptyBarBreakdownChart.tsx index 724e736f80..b24d16507f 100644 --- a/web/src/features/charts/bar-breakdown/EmptyBarBreakdownChart.tsx +++ b/web/src/features/charts/bar-breakdown/EmptyBarBreakdownChart.tsx @@ -1,3 +1,4 @@ +import { Group } from '@visx/group'; import { scaleLinear } from 'd3-scale'; import { useMemo } from 'react'; import { modeOrderBarBreakdown } from 'utils/constants'; @@ -12,45 +13,34 @@ import { getDataBlockPositions } from './utils'; interface EmptyBarBreakdownChartProps { height: number; width: number; - isMobile?: boolean; + isMobile: boolean; overLayText?: string; } +const MAX_CO2EQ = 10; +const MIN_CO2EQ = -1; + +const formatTick = (t: number) => + // TODO: format tick depending on displayByEmissions + `${t} ${PowerUnits.GIGAWATTS}`; + function EmptyBarBreakdownChart({ height, isMobile, overLayText, width, }: EmptyBarBreakdownChartProps) { - const productionData = modeOrderBarBreakdown.map((d) => ({ - mode: d, - gCo2eq: 0, - gCo2eqByFuel: {}, - gCo2eqByFuelAndSource: {}, - isStorage: false, - })); const { productionY } = getDataBlockPositions(0, []); - const maxCO2eqExport = 1; - const maxCO2eqImport = 10; - const maxCO2eqProduction = 10; - // in CO₂eq const co2Scale = useMemo( () => scaleLinear() - .domain([ - -maxCO2eqExport || 0, - Math.max(maxCO2eqProduction || 0, maxCO2eqImport || 0), - ]) + .domain([MIN_CO2EQ, MAX_CO2EQ]) .range([0, width - LABEL_MAX_WIDTH - PADDING_X]), - [maxCO2eqExport, maxCO2eqProduction, maxCO2eqImport, width] + [width] ); - // eslint-disable-next-line unicorn/consistent-function-scoping - const formatTick = (t: number) => - // TODO: format tick depending on displayByEmissions - `${t} ${PowerUnits.GIGAWATTS}`; return ( <>
@@ -67,16 +57,16 @@ function EmptyBarBreakdownChart({ height={height} > - - {productionData.map((d, index) => ( + + {modeOrderBarBreakdown.map((mode, index) => ( ))} - + ); diff --git a/web/src/features/charts/bar-breakdown/utils.test.ts b/web/src/features/charts/bar-breakdown/utils.test.ts index 0291fe6559..82e13a142a 100644 --- a/web/src/features/charts/bar-breakdown/utils.test.ts +++ b/web/src/features/charts/bar-breakdown/utils.test.ts @@ -313,7 +313,7 @@ describe('getExchangesToDisplay', () => { exchange: { AT: -934, BE: 934, NO: -934, 'NO-NO2': -500 }, }, }; - const result = getExchangesToDisplay('DE', true, ZoneStates); + const result = getExchangesToDisplay(true, 'DE', ZoneStates); expect(result).to.deep.eq(['AT', 'BE', 'NO']); }); it('shows non-aggregated exchanges only when required', () => { @@ -323,7 +323,7 @@ describe('getExchangesToDisplay', () => { exchange: { AT: -934, BE: 934, NO: -934, 'NO-NO2': -500 }, }, }; - const result = getExchangesToDisplay('DE', false, ZoneStates); + const result = getExchangesToDisplay(false, 'DE', ZoneStates); expect(result).to.deep.eq(['AT', 'BE', 'NO-NO2']); }); it('handles empty exchange', () => { @@ -333,7 +333,7 @@ describe('getExchangesToDisplay', () => { exchange: {}, }, }; - const result = getExchangesToDisplay('DE', false, ZoneStates); + const result = getExchangesToDisplay(false, 'DE', ZoneStates); expect(result).to.deep.eq([]); }); }); @@ -346,7 +346,7 @@ describe('getExchangeData', () => { exchange: { AT: -934, ES: 934 }, exchangeCapacities: { ES: exchangeCapacity, AT: exchangeCapacity }, }; - const result = getExchangeData(exchangeCapacitiesZoneDetailsData, ['ES', 'AT'], true); + const result = getExchangeData(['ES', 'AT'], true, exchangeCapacitiesZoneDetailsData); expect(result).to.deep.eq([ { @@ -369,7 +369,7 @@ describe('getExchangeData', () => { const exchangeCapacitiesZoneDetailsData = { ...zoneDetailsData, }; - const result = getExchangeData(exchangeCapacitiesZoneDetailsData, ['ES'], true); + const result = getExchangeData(['ES'], true, exchangeCapacitiesZoneDetailsData); expect(result).to.deep.eq([ { @@ -388,7 +388,7 @@ describe('getExchangeData', () => { exchange: {}, exchangeCapacity: { ES: exchangeCapacity }, }; - const result = getExchangeData(exchangeCapacitiesZoneDetailsData, ['ES'], true); + const result = getExchangeData(['ES'], true, exchangeCapacitiesZoneDetailsData); expect(result).to.deep.equal([ { diff --git a/web/src/features/charts/bar-breakdown/utils.ts b/web/src/features/charts/bar-breakdown/utils.ts index 0c7911189c..e21fd632eb 100644 --- a/web/src/features/charts/bar-breakdown/utils.ts +++ b/web/src/features/charts/bar-breakdown/utils.ts @@ -49,8 +49,11 @@ export interface ProductionDataType { gCo2eq: number; } -export const getProductionData = (data: ZoneDetail): ProductionDataType[] => - modeOrderBarBreakdown.map((mode) => { +export const getProductionData = (data?: ZoneDetail): ProductionDataType[] => { + if (!data) { + return []; + } + return modeOrderBarBreakdown.map((mode) => { const isStorage = mode.includes('storage'); const generationMode = mode.replace(' storage', '') as GenerationType; // Power in MW @@ -73,7 +76,7 @@ export const getProductionData = (data: ZoneDetail): ProductionDataType[] => gCo2eq, }; }); - +}; interface GetElectricityProductionValueType { capacity: Maybe; isStorage: boolean; @@ -137,11 +140,14 @@ export interface ExchangeDataType { exchangeCapacityRange: number[]; } export const getExchangeData = ( - data: ZoneDetail, exchangeKeys: ZoneKey[], - isConsumption: boolean -): ExchangeDataType[] => - exchangeKeys.map((zoneKey: ZoneKey) => { + isConsumption: boolean, + data?: ZoneDetail +): ExchangeDataType[] => { + if (!data || !isConsumption) { + return []; + } + return exchangeKeys.map((zoneKey: ZoneKey) => { // Power in MW const exchange = data.exchange?.[zoneKey]; const exchangeCapacityRange = data.exchangeCapacities?.[zoneKey] ?? [0, 0]; @@ -158,14 +164,19 @@ export const getExchangeData = ( gCo2eq, }; }); +}; export const getExchangesToDisplay = ( - currentZoneKey: ZoneKey, isCountryView: boolean, - zoneStates: { + currentZoneKey?: ZoneKey, + zoneStates?: { [key: string]: ZoneDetail; } ): ZoneKey[] => { + if (!currentZoneKey || !zoneStates) { + return []; + } + const exchangeKeysToRemove = isCountryView ? exchangesToExclude.exchangesToExcludeCountryView : exchangesToExclude.exchangesToExcludeZoneView; diff --git a/web/src/features/charts/hooks/useBarElectricityBreakdownChartData.ts b/web/src/features/charts/hooks/useBarElectricityBreakdownChartData.ts index 1d1367d5e0..dfd9f957df 100644 --- a/web/src/features/charts/hooks/useBarElectricityBreakdownChartData.ts +++ b/web/src/features/charts/hooks/useBarElectricityBreakdownChartData.ts @@ -1,5 +1,6 @@ import useGetZone from 'api/getZone'; import { useAtomValue } from 'jotai'; +import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { RouteParameters } from 'types'; import { SpatialAggregate } from 'utils/constants'; @@ -27,6 +28,19 @@ export default function useBarBreakdownChartData() { const isCountryView = viewMode === SpatialAggregate.COUNTRY; const currentData = zoneData?.zoneStates?.[selectedDatetimeString]; const isConsumption = useAtomValue(isConsumptionAtom); + + const productionData = useMemo(() => getProductionData(currentData), [currentData]); + + const exchangeKeys = useMemo( + () => getExchangesToDisplay(isCountryView, zoneId, zoneData?.zoneStates), + [isCountryView, zoneId, zoneData?.zoneStates] + ); + + const exchangeData = useMemo( + () => getExchangeData(exchangeKeys, isConsumption, currentData), + [exchangeKeys, isConsumption, currentData] + ); + if (isLoading) { return { isLoading }; } @@ -42,13 +56,6 @@ export default function useBarBreakdownChartData() { }; } - const exchangeKeys = getExchangesToDisplay(zoneId, isCountryView, zoneData.zoneStates); - - const productionData = getProductionData(currentData); // TODO: Consider memoing this - const exchangeData = isConsumption - ? getExchangeData(currentData, exchangeKeys, isConsumption) - : []; // TODO: Consider memoing this - const { exchangeY } = getDataBlockPositions( //TODO this naming could be more descriptive productionData.length, diff --git a/web/src/features/charts/hooks/useOriginChartData.ts b/web/src/features/charts/hooks/useOriginChartData.ts index dd8454389c..b821442623 100644 --- a/web/src/features/charts/hooks/useOriginChartData.ts +++ b/web/src/features/charts/hooks/useOriginChartData.ts @@ -53,8 +53,8 @@ export default function useOriginChartData() { } const exchangesForSelectedAggregate = getExchangesToDisplay( - zoneId, isCountryView, + zoneId, zoneData.zoneStates );