frontend/src/charts/BinInventoryChart.tsx
2025-03-10 13:33:55 -06:00

213 lines
5.5 KiB
TypeScript

import { Box, Chip, useTheme } from "@mui/material";
import { GrainColour } from "grain";
import { pond } from "protobuf-ts/pond";
import { useEffect, useState } from "react";
import { Cell, Legend, Pie, PieChart, ResponsiveContainer, Sector } from "recharts";
import { stringToMaterialColour } from "utils";
export interface GrainAmount {
grain: pond.Grain;
grainName: string;
bushelAmount: number;
bushelsPerTonne?: number;
}
interface Props {
inventory: GrainAmount[];
onClick: (grain: pond.Grain, grainName: string) => void;
activeGrain?: pond.Grain | null;
customLabel?: string;
customUnit?: string;
}
export default function BinInventoryChart(props: Props) {
const { inventory, onClick, activeGrain, customLabel, customUnit } = props;
const theme = useTheme();
const [activeIndex, setActiveIndex] = useState(0);
useEffect(() => {
let newIndex = inventory.findIndex(g => g.grain === activeGrain);
setActiveIndex(newIndex);
}, [inventory, activeGrain]);
const renderNoGrain = (props: any) => {
const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, fill, payload } = props;
return (
<g>
<text x={cx} y={cy} dy={-2} textAnchor="middle" fill={fill}>
{payload.grainName}
</text>
<text
x={cx}
y={cy}
dy={12}
textAnchor="middle"
fill={theme.palette.text.secondary}
fontSize="0.7rem">
Please make bin
</text>
<Sector
cx={cx}
cy={cy}
innerRadius={innerRadius}
outerRadius={outerRadius}
startAngle={startAngle}
endAngle={endAngle}
fill={fill}
/>
<Sector
cx={cx}
cy={cy}
startAngle={startAngle}
endAngle={endAngle}
innerRadius={innerRadius - 8}
outerRadius={innerRadius - 5}
fill={fill}
/>
</g>
);
};
const renderActiveGrain = (props: any) => {
const {
cx,
cy,
innerRadius,
outerRadius,
startAngle,
endAngle,
fill,
payload,
percent
} = props;
return (
<g>
<text x={cx} y={cy} dy={-8} textAnchor="middle" fill={fill}>
{payload.grainName}
</text>
<text
x={cx}
y={cy}
dy={6}
textAnchor="middle"
fill={theme.palette.text.secondary}
fontSize="0.7rem">
{`${(percent * 100).toFixed(2)}%`}
</text>
<text
x={cx}
y={cy}
dy={20}
textAnchor="middle"
fill={theme.palette.text.secondary}
fontSize="0.7rem">
{payload.bushelAmount.toLocaleString() + (customUnit ? customUnit : " bu")}
</text>
<Sector
cx={cx}
cy={cy}
innerRadius={innerRadius}
outerRadius={outerRadius}
startAngle={startAngle}
endAngle={endAngle}
fill={fill}
/>
<Sector
cx={cx}
cy={cy}
startAngle={startAngle}
endAngle={endAngle}
innerRadius={innerRadius - 8}
outerRadius={innerRadius - 5}
fill={fill}
/>
</g>
);
};
const pieChart = () => {
if (inventory.length > 0) {
return (
<PieChart>
{customLabel && (
<Legend
verticalAlign="top"
content={() => (
<Box textAlign="center" marginY={0.5}>
<Chip label={customLabel} />
</Box>
)}
/>
)}
<Pie
cursor="pointer"
isAnimationActive={false}
dataKey="bushelAmount"
data={inventory}
cx="50%"
cy="50%"
innerRadius="70%"
outerRadius="100%"
stroke="transparent"
paddingAngle={3}
labelLine={false}
activeIndex={activeIndex === -1 ? 0 : activeIndex}
activeShape={(props: any) => renderActiveGrain(props)}
onMouseEnter={(_data: any, index: number) => setActiveIndex(index)}
onClick={(data: any, _index: number) => {
onClick(data.grain as pond.Grain, data.grainName);
}}>
{inventory.map((g, index) => (
<Cell
key={`cell-${index}`}
fill={
g.grain === pond.Grain.GRAIN_CUSTOM
? stringToMaterialColour(g.grainName)
: GrainColour(g.grain)
}
/>
))}
</Pie>
</PieChart>
);
}
let inv: any = [{ grain: 0, bushelAmount: 1 }];
return (
<PieChart>
{customLabel && (
<Legend
verticalAlign="top"
content={() => (
<Box textAlign="center" marginY={0.5}>
<Chip label={customLabel} />
</Box>
)}
/>
)}
<Pie
cursor="pointer"
isAnimationActive={false}
dataKey="bushelAmount"
data={inv}
cx="50%"
cy="50%"
innerRadius="70%"
outerRadius="100%"
stroke="transparent"
paddingAngle={3}
labelLine={false}
activeIndex={0}
activeShape={(props: any) => renderNoGrain(props)}>
Please Create Bin
</Pie>
</PieChart>
);
};
return (
<Box height={1} width={1}>
<ResponsiveContainer>{pieChart()}</ResponsiveContainer>
</Box>
);
}