Skip to content

Commit

Permalink
perf: Memoize BarBreakdownChart.tsx (#7674)
Browse files Browse the repository at this point in the history
* perf: Memoize BarBreakdownChart.tsx

* simplify EmptyBarBreakdownChart.tsx
  • Loading branch information
VIKTORVAV99 authored Jan 3, 2025
1 parent 8d89e06 commit 9e8130f
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 84 deletions.
59 changes: 30 additions & 29 deletions web/src/features/charts/bar-breakdown/BarBreakdownChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 (
<div className="text-md relative w-full" ref={ref}>
<RoundedCard ref={ref}>
<ChartTitle className="opacity-40" id={Charts.BAR_BREAKDOWN_CHART} />
<EmptyBarBreakdownChart
height={height}
width={width}
overLayText={t('country-panel.noDataAtTimestamp')}
isMobile={isMobile}
/>
</div>
</RoundedCard>
);
}

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 (
<RoundedCard ref={ref}>
<ChartTitle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { max as d3Max, min as d3Min } from 'd3-array';
import { scaleLinear } from 'd3-scale';
import { useCo2ColorScale } from 'hooks/theme';
import { useAtomValue } from 'jotai';
import { useCallback, useMemo } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { ElectricityModeType, ZoneDetails, ZoneKey } from 'types';
import { formatEnergy, formatPower } from 'utils/formatting';
import { isHourlyAtom } from 'utils/state/atoms';

import BarElectricityExchangeChart from './BarElectricityExchangeChart';
import { BarElectricityProductionChart } from './BarElectricityProductionChart';
import BarElectricityProductionChart from './BarElectricityProductionChart';
import { EXCHANGE_PADDING, LABEL_MAX_WIDTH, PADDING_X } from './constants';
import { FormatTick } from './elements/Axis';
import { ExchangeDataType, getDataBlockPositions, ProductionDataType } from './utils';
Expand Down Expand Up @@ -136,4 +136,4 @@ function BarElectricityBreakdownChart({
);
}

export default BarElectricityBreakdownChart;
export default memo(BarElectricityBreakdownChart);
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ScaleLinear } from 'd3-scale';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { ElectricityModeType } from 'types';
import { modeColor } from 'utils/constants';
Expand All @@ -9,7 +10,7 @@ import HorizontalBar from './elements/HorizontalBar';
import { ProductionSourceRow } from './elements/Row';
import { getElectricityProductionValue, ProductionDataType } from './utils';

export function BarElectricityProductionChart({
function BarElectricityProductionChart({
powerScale,
height,
formatTick,
Expand Down Expand Up @@ -74,3 +75,5 @@ export function BarElectricityProductionChart({
</svg>
);
}

export default memo(BarElectricityProductionChart);
46 changes: 18 additions & 28 deletions web/src/features/charts/bar-breakdown/EmptyBarBreakdownChart.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Group } from '@visx/group';
import { scaleLinear } from 'd3-scale';
import { useMemo } from 'react';
import { modeOrderBarBreakdown } from 'utils/constants';
Expand All @@ -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 (
<>
<div style={{ width, height, position: 'absolute' }}>
Expand All @@ -67,16 +57,16 @@ function EmptyBarBreakdownChart({
height={height}
>
<Axis formatTick={formatTick} height={height} scale={co2Scale} />
<g transform={`translate(0, ${productionY})`}>
{productionData.map((d, index) => (
<Group top={productionY}>
{modeOrderBarBreakdown.map((mode, index) => (
<ProductionSourceRow
key={d.mode}
key={mode}
index={index}
productionMode={d.mode}
productionMode={mode}
width={width}
scale={co2Scale}
value={Math.abs(d.gCo2eq)}
isMobile={Boolean(isMobile)}
value={0}
isMobile={isMobile}
>
<HorizontalBar
className="production"
Expand All @@ -86,7 +76,7 @@ function EmptyBarBreakdownChart({
/>
</ProductionSourceRow>
))}
</g>
</Group>
</svg>
</>
);
Expand Down
12 changes: 6 additions & 6 deletions web/src/features/charts/bar-breakdown/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -333,7 +333,7 @@ describe('getExchangesToDisplay', () => {
exchange: {},
},
};
const result = getExchangesToDisplay('DE', false, ZoneStates);
const result = getExchangesToDisplay(false, 'DE', ZoneStates);
expect(result).to.deep.eq([]);
});
});
Expand All @@ -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([
{
Expand All @@ -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([
{
Expand All @@ -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([
{
Expand Down
29 changes: 20 additions & 9 deletions web/src/features/charts/bar-breakdown/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -73,7 +76,7 @@ export const getProductionData = (data: ZoneDetail): ProductionDataType[] =>
gCo2eq,
};
});

};
interface GetElectricityProductionValueType {
capacity: Maybe<number>;
isStorage: boolean;
Expand Down Expand Up @@ -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];
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 };
}
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion web/src/features/charts/hooks/useOriginChartData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ export default function useOriginChartData() {
}

const exchangesForSelectedAggregate = getExchangesToDisplay(
zoneId,
isCountryView,
zoneId,
zoneData.zoneStates
);

Expand Down

0 comments on commit 9e8130f

Please sign in to comment.