Initial commit: Plane
Some checks failed
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Admin Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Sync Repositories / sync_changes (push) Has been cancelled
Some checks failed
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Admin Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Sync Repositories / sync_changes (push) Has been cancelled
Synced from upstream: 8853637e981ed7d8a6cff32bd98e7afe20f54362
This commit is contained in:
209
packages/propel/src/charts/bar-chart/root.tsx
Normal file
209
packages/propel/src/charts/bar-chart/root.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
"use client";
|
||||
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import {
|
||||
BarChart as CoreBarChart,
|
||||
Bar,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Legend,
|
||||
CartesianGrid,
|
||||
} from "recharts";
|
||||
// plane imports
|
||||
import { AXIS_LABEL_CLASSNAME } from "@plane/constants";
|
||||
import { TBarChartProps } from "@plane/types";
|
||||
// local components
|
||||
import { getLegendProps } from "../components/legend";
|
||||
import { CustomXAxisTick, CustomYAxisTick } from "../components/tick";
|
||||
import { CustomTooltip } from "../components/tooltip";
|
||||
import { barShapeVariants } from "./bar";
|
||||
|
||||
const DEFAULT_BAR_FILL_COLOR = "#000000";
|
||||
|
||||
export const BarChart = React.memo(<K extends string, T extends string>(props: TBarChartProps<K, T>) => {
|
||||
const {
|
||||
data,
|
||||
bars,
|
||||
xAxis,
|
||||
yAxis,
|
||||
barSize = 40,
|
||||
className,
|
||||
legend,
|
||||
margin,
|
||||
tickCount = {
|
||||
x: undefined,
|
||||
y: 10,
|
||||
},
|
||||
customTicks,
|
||||
showTooltip = true,
|
||||
customTooltipContent,
|
||||
} = props;
|
||||
// states
|
||||
const [activeBar, setActiveBar] = useState<string | null>(null);
|
||||
const [activeLegend, setActiveLegend] = useState<string | null>(null);
|
||||
|
||||
// derived values
|
||||
const { stackKeys, stackLabels } = useMemo(() => {
|
||||
const keys: string[] = [];
|
||||
const labels: Record<string, string> = {};
|
||||
|
||||
for (const bar of bars) {
|
||||
keys.push(bar.key);
|
||||
labels[bar.key] = bar.label;
|
||||
}
|
||||
|
||||
return { stackKeys: keys, stackLabels: labels };
|
||||
}, [bars]);
|
||||
|
||||
// get bar color dynamically based on payload
|
||||
const getBarColor = useCallback(
|
||||
(payload: Record<string, string>[], barKey: string) => {
|
||||
const bar = bars.find((b) => b.key === barKey);
|
||||
if (!bar) return DEFAULT_BAR_FILL_COLOR;
|
||||
|
||||
if (typeof bar.fill === "function") {
|
||||
const payloadItem = payload?.find((item) => item.dataKey === barKey);
|
||||
if (payloadItem?.payload) {
|
||||
try {
|
||||
return bar.fill(payloadItem.payload);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return DEFAULT_BAR_FILL_COLOR;
|
||||
}
|
||||
} else {
|
||||
return DEFAULT_BAR_FILL_COLOR; // fallback color when no payload data
|
||||
}
|
||||
} else {
|
||||
return bar.fill;
|
||||
}
|
||||
},
|
||||
[bars]
|
||||
);
|
||||
|
||||
// get all bar colors
|
||||
const getAllBarColors = useCallback(
|
||||
(payload: any[]) => {
|
||||
const colors: Record<string, string> = {};
|
||||
for (const bar of bars) {
|
||||
colors[bar.key] = getBarColor(payload, bar.key);
|
||||
}
|
||||
return colors;
|
||||
},
|
||||
[bars, getBarColor]
|
||||
);
|
||||
|
||||
const renderBars = useMemo(
|
||||
() =>
|
||||
bars.map((bar) => (
|
||||
<Bar
|
||||
key={bar.key}
|
||||
dataKey={bar.key}
|
||||
stackId={bar.stackId}
|
||||
opacity={!!activeLegend && activeLegend !== bar.key ? 0.1 : 1}
|
||||
shape={(shapeProps: any) => {
|
||||
const shapeVariant = barShapeVariants[bar.shapeVariant ?? "bar"];
|
||||
const node = shapeVariant(shapeProps, bar, stackKeys);
|
||||
return React.isValidElement(node) ? node : <>{node}</>;
|
||||
}}
|
||||
className="[&_path]:transition-opacity [&_path]:duration-200"
|
||||
onMouseEnter={() => setActiveBar(bar.key)}
|
||||
onMouseLeave={() => setActiveBar(null)}
|
||||
fill={getBarColor(data, bar.key)}
|
||||
/>
|
||||
)),
|
||||
[activeLegend, stackKeys, bars, getBarColor, data]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<CoreBarChart
|
||||
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,
|
||||
}}
|
||||
barSize={barSize}
|
||||
className="recharts-wrapper"
|
||||
>
|
||||
<CartesianGrid stroke="rgba(var(--color-border-100), 0.8)" vertical={false} />
|
||||
<XAxis
|
||||
dataKey={xAxis.key}
|
||||
tick={(props) => {
|
||||
const TickComponent = customTicks?.x || CustomXAxisTick;
|
||||
return <TickComponent {...props} />;
|
||||
}}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
label={{
|
||||
value: xAxis.label,
|
||||
dy: xAxis.dy ?? 28,
|
||||
className: AXIS_LABEL_CLASSNAME,
|
||||
}}
|
||||
tickCount={tickCount.x}
|
||||
/>
|
||||
<YAxis
|
||||
domain={yAxis.domain}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
label={{
|
||||
value: yAxis.label,
|
||||
angle: -90,
|
||||
position: "bottom",
|
||||
offset: yAxis.offset ?? -24,
|
||||
dx: yAxis.dx ?? -16,
|
||||
className: AXIS_LABEL_CLASSNAME,
|
||||
}}
|
||||
tick={(props) => {
|
||||
const TickComponent = customTicks?.y || CustomYAxisTick;
|
||||
return <TickComponent {...props} />;
|
||||
}}
|
||||
tickCount={tickCount.y}
|
||||
allowDecimals={!!yAxis.allowDecimals}
|
||||
/>
|
||||
{legend && (
|
||||
// @ts-expect-error recharts types are not up to date
|
||||
<Legend
|
||||
onMouseEnter={(payload) => setActiveLegend(payload.value)}
|
||||
onMouseLeave={() => setActiveLegend(null)}
|
||||
formatter={(value) => stackLabels[value]}
|
||||
{...getLegendProps(legend)}
|
||||
/>
|
||||
)}
|
||||
{showTooltip && (
|
||||
<Tooltip
|
||||
cursor={{
|
||||
fill: "currentColor",
|
||||
className: "text-custom-background-90/80 cursor-pointer",
|
||||
}}
|
||||
wrapperStyle={{
|
||||
pointerEvents: "auto",
|
||||
}}
|
||||
content={({ active, label, payload }) => {
|
||||
if (customTooltipContent) return customTooltipContent({ active, label, payload });
|
||||
return (
|
||||
<CustomTooltip
|
||||
active={active}
|
||||
label={label}
|
||||
payload={payload}
|
||||
activeKey={activeBar}
|
||||
itemKeys={stackKeys}
|
||||
itemLabels={stackLabels}
|
||||
itemDotColors={getAllBarColors(payload || [])}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{renderBars}
|
||||
</CoreBarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
BarChart.displayName = "BarChart";
|
||||
Reference in New Issue
Block a user