152 lines
5.0 KiB
TypeScript
152 lines
5.0 KiB
TypeScript
"use client";
|
|
|
|
import React, { useMemo, useState } from "react";
|
|
import { Cell, PieChart as CorePieChart, Label, Legend, Pie, ResponsiveContainer, Tooltip } from "recharts";
|
|
// plane imports
|
|
import { TPieChartProps } from "@plane/types";
|
|
// local components
|
|
import { getLegendProps } from "../components/legend";
|
|
import { CustomActiveShape } from "./active-shape";
|
|
import { CustomPieChartTooltip } from "./tooltip";
|
|
|
|
export const PieChart = React.memo(<K extends string, T extends string>(props: TPieChartProps<K, T>) => {
|
|
const {
|
|
data,
|
|
dataKey,
|
|
cells,
|
|
className,
|
|
innerRadius,
|
|
legend,
|
|
margin,
|
|
outerRadius,
|
|
showTooltip = true,
|
|
showLabel,
|
|
customLabel,
|
|
centerLabel,
|
|
cornerRadius,
|
|
paddingAngle,
|
|
tooltipLabel,
|
|
} = props;
|
|
// states
|
|
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
|
const [activeLegend, setActiveLegend] = useState<string | null>(null);
|
|
|
|
const renderCells = useMemo(
|
|
() =>
|
|
cells.map((cell, index) => (
|
|
<Cell
|
|
key={cell.key}
|
|
className="transition-opacity duration-200"
|
|
fill={cell.fill}
|
|
opacity={!!activeLegend && activeLegend !== cell.key ? 0.1 : 1}
|
|
style={{
|
|
outline: "none",
|
|
}}
|
|
onMouseEnter={() => setActiveIndex(index)}
|
|
onMouseLeave={() => setActiveIndex(null)}
|
|
/>
|
|
)),
|
|
[activeLegend, cells]
|
|
);
|
|
|
|
return (
|
|
<div className={className}>
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<CorePieChart
|
|
data={data}
|
|
margin={{
|
|
top: margin?.top === undefined ? 5 : margin.top,
|
|
right: margin?.right === undefined ? 30 : margin.right,
|
|
bottom: margin?.bottom === undefined ? 5 : margin.bottom,
|
|
left: margin?.left === undefined ? 20 : margin.left,
|
|
}}
|
|
>
|
|
<Pie
|
|
activeIndex={activeIndex === null ? undefined : activeIndex}
|
|
onMouseLeave={() => setActiveIndex(null)}
|
|
data={data}
|
|
dataKey={dataKey}
|
|
cx="50%"
|
|
cy="50%"
|
|
blendStroke
|
|
activeShape={<CustomActiveShape />}
|
|
innerRadius={innerRadius}
|
|
outerRadius={outerRadius}
|
|
cornerRadius={cornerRadius}
|
|
paddingAngle={paddingAngle}
|
|
labelLine={false}
|
|
label={
|
|
showLabel
|
|
? ({ payload, ...props }) => (
|
|
<text
|
|
className="text-sm font-medium transition-opacity duration-200"
|
|
cx={props.cx}
|
|
cy={props.cy}
|
|
x={props.x}
|
|
y={props.y}
|
|
textAnchor={props.textAnchor}
|
|
dominantBaseline={props.dominantBaseline}
|
|
fill="rgba(var(--color-text-200))"
|
|
opacity={!!activeLegend && activeLegend !== payload.key ? 0.1 : 1}
|
|
>
|
|
{customLabel?.(payload.count) ?? payload.count}
|
|
</text>
|
|
)
|
|
: undefined
|
|
}
|
|
>
|
|
{renderCells}
|
|
{centerLabel && (
|
|
<Label
|
|
value={centerLabel.text}
|
|
fill={centerLabel.fill}
|
|
position="center"
|
|
opacity={activeLegend ? 0.1 : 1}
|
|
style={centerLabel.style}
|
|
className={centerLabel.className}
|
|
/>
|
|
)}
|
|
</Pie>
|
|
{legend && (
|
|
// @ts-expect-error recharts types are not up to date
|
|
<Legend
|
|
onMouseEnter={(payload) => {
|
|
// @ts-expect-error recharts types are not up to date
|
|
const key: string | undefined = payload.payload?.key;
|
|
if (!key) return;
|
|
setActiveLegend(key);
|
|
setActiveIndex(null);
|
|
}}
|
|
onMouseLeave={() => setActiveLegend(null)}
|
|
{...getLegendProps(legend)}
|
|
/>
|
|
)}
|
|
{showTooltip && (
|
|
<Tooltip
|
|
cursor={{
|
|
fill: "currentColor",
|
|
className: "text-custom-background-90/80 cursor-pointer",
|
|
}}
|
|
wrapperStyle={{
|
|
pointerEvents: "none",
|
|
}}
|
|
content={({ active, payload }) => {
|
|
if (!active || !payload || !payload.length) return null;
|
|
const cellData = cells.find((c) => c.key === payload[0].payload.key);
|
|
if (!cellData) return null;
|
|
const label = tooltipLabel
|
|
? typeof tooltipLabel === "function"
|
|
? tooltipLabel(payload[0]?.payload?.payload)
|
|
: tooltipLabel
|
|
: dataKey;
|
|
return <CustomPieChartTooltip dotColor={cellData.fill} label={label} payload={payload} />;
|
|
}}
|
|
/>
|
|
)}
|
|
</CorePieChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
);
|
|
});
|
|
PieChart.displayName = "PieChart";
|