Skip to content

Commit

Permalink
Merge pull request #1031 from Open-Earth-Foundation/nina/feat/ON-2864…
Browse files Browse the repository at this point in the history
…/emissionsForecastChart

[ON-2864] emissions forecast chart
  • Loading branch information
thehighestprimenumber authored Jan 5, 2025
2 parents 87b0df4 + ddd444c commit 435f5d8
Show file tree
Hide file tree
Showing 24 changed files with 2,102 additions and 657 deletions.
66 changes: 66 additions & 0 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@huggingface/inference": "^2.8.0",
"@next/env": "^14.2.5",
"@nivo/bar": "^0.88.0",
"@nivo/line": "^0.88.0",
"@react-email/components": "^0.0.19",
"@reduxjs/toolkit": "^2.2.7",
"@storybook/cli": "^8.3.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { EmissionsForecastData } from "@/util/types";
import { TFunction } from "i18next/typescript/t";
import { useState } from "react";
import { GrowthRatesExplanationModal } from "@/app/[lng]/[inventory]/InventoryResultTab/EmissionsForecast/GrowthRatesExplanationModal";
import {
Card,
CardBody,
CardHeader,
HStack,
IconButton,
Text,
} from "@chakra-ui/react";
import { InfoOutlineIcon } from "@chakra-ui/icons";
import { EmissionsForecastChart } from "@/app/[lng]/[inventory]/InventoryResultTab/EmissionsForecast/EmissionsForecastChart";

export const EmissionsForecastCard = ({
forecast,
t,
lng,
}: {
forecast: EmissionsForecastData;
t: TFunction;
lng: string;
}) => {
const [isExplanationModalOpen, setIsExplanationModalOpen] = useState(false);

return (
<>
<GrowthRatesExplanationModal
t={t}
isOpen={isExplanationModalOpen}
onClose={() => setIsExplanationModalOpen(false)}
emissionsForecast={forecast}
lng={lng}
/>

<Card paddingY="0px" paddingX="0px" height="100%" width="100%">
<CardHeader>
<HStack justifyContent="space-between">
<Text fontFamily="heading" fontSize="title.md" fontWeight="medium">
{t("breakdown-of-sub-sector-emissions")}
</Text>
<IconButton
width={"20px"}
height={"20px"}
variant={"unstyled"}
isRound
onClick={() => setIsExplanationModalOpen(true)}
icon={<InfoOutlineIcon marginRight={3} fontSize={"20px"} />}
aria-label={"growth-rates-explanation"}
/>
</HStack>
</CardHeader>
<CardBody
paddingY="0px"
paddingLeft={4}
paddingRight={0}
height="100%"
width="100%"
>
<EmissionsForecastChart forecast={forecast} t={t} />
</CardBody>
</Card>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { EmissionsForecastData } from "@/util/types";
import { TFunction } from "i18next/typescript/t";
import { getReferenceNumberByName, SECTORS, ISector } from "@/util/constants";
import {
Badge,
Box,
Card,
Heading,
Table,
Tbody,
Td,
Text,
Th,
Tr,
} from "@chakra-ui/react";
import { ResponsiveLine } from "@nivo/line";
import { convertKgToTonnes } from "@/util/helpers";

interface LineChartData {
id: string;
data: { x: string; y: number }[];
}

export const EmissionsForecastChart = ({
forecast,
t,
}: {
forecast: EmissionsForecastData;
t: TFunction;
}) => {
const convertToLineChartData = (
forecastData: EmissionsForecastData,
): LineChartData[] => {
const sectors = Object.keys(
forecastData.forecast[Object.keys(forecastData.forecast)[0]],
);

return sectors
.map((sector) => ({
id: t(
SECTORS.find((s) => s.referenceNumber === sector)?.name + "-short" ||
sector,
),
data: Object.entries(forecastData.forecast).map(
([year, sectorsData]) => {
return {
x: year,
y: sectorsData[sector] || 0,
};
},
),
}))
.reverse();
};

const data = convertToLineChartData(forecast);

const colors = ["#FFAB51", "#5162FF", "#51ABFF", "#D45252", "#CFAE53"];
return (
<ResponsiveLine
data={data}
margin={{ top: 50, right: 30, bottom: 100, left: 80 }}
xScale={{ type: "point" }}
yScale={{
type: "linear",
min: 0,
max: "auto",
stacked: true,
}}
curve="natural"
axisBottom={{
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
format: (value) => (parseInt(value) % 5 === 0 ? value : ""),
}}
axisLeft={{
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
format: (value: number) => convertKgToTonnes(value),
}}
colors={colors}
tooltip={({ point }) => {
const year = point.data.x;
const sumOfYs = data.reduce((sum, series) => {
const yearData = series.data.find(({ x }) => x === year);
return sum + parseInt((yearData?.y as unknown as string) || "0");
}, 0);

return (
<Card py={2} px={2}>
<Box padding="4" borderBottom="1px solid">
<Heading size="title.sm">{t("year")}</Heading>
<Text
fontFamily="heading"
fontSize="label.lg"
fontStyle="normal"
lineHeight="20px"
letterSpacing="wide"
>
{year as unknown as string}
</Text>
</Box>
<Box padding="4">
<Table variant="simple" size={"sm"}>
<Tbody>
{data.map((series, index) => {
const yearData = series.data.find(
({ x }) => x === point.data.x,
);
const percentage = yearData
? ((yearData.y / sumOfYs) * 100).toFixed(2)
: 0;
const sectorRefNo = getReferenceNumberByName(
point.serieId as keyof ISector,
);

return (
<Tr key={series.id}>
<Td>
<Badge
colorScheme="gray"
boxSize="10px"
bg={colors[index]}
marginRight="8px"
/>
{series.id}
</Td>
<Td>
{
forecast.growthRates[point.data.x as number]?.[
sectorRefNo!
]
}
</Td>
<Td>{percentage}%</Td>
<Td>
{convertKgToTonnes(
parseInt(yearData?.y as unknown as string),
)}
</Td>
</Tr>
);
})}
<Tr>
<Th>{t("total")}</Th>
<Th></Th>
<Th>{convertKgToTonnes(sumOfYs)}</Th>
</Tr>
</Tbody>
</Table>
</Box>
</Card>
);
}}
enableGridX={false}
enableGridY={false}
enablePoints={false}
pointSize={10}
pointColor={{ theme: "background" }}
pointBorderWidth={2}
pointBorderColor={{ from: "serieColor" }}
pointLabel="data.yFormatted"
pointLabelYOffset={-12}
enableArea={true}
areaOpacity={1}
enableTouchCrosshair={true}
useMesh={true}
legends={[
{
anchor: "bottom",
direction: "row",
justify: false,
translateX: 0,
translateY: 60,
itemWidth: 140,
itemHeight: 20,
itemsSpacing: 4,
symbolSize: 20,
symbolShape: "circle",
itemDirection: "left-to-right",
itemTextColor: "#777",
effects: [
{
on: "hover",
style: {
itemBackground: "rgba(0, 0, 0, .03)",
itemOpacity: 1,
},
},
],
},
]}
/>
);
};
Loading

0 comments on commit 435f5d8

Please sign in to comment.