Skip to content

Commit 51ed202

Browse files
committed
Rework ActionsPieChart with dynamic color palettes
The colors in the pie chart now changes with dark/light mode for better visibility. Additionally there is an added optional parameter `href` to each pie chart item, which turns the corresponding legend entry into a link.
1 parent 1e26e86 commit 51ed202

File tree

8 files changed

+155
-130
lines changed

8 files changed

+155
-130
lines changed

frontend/src/components/ActionCacheMissMetrics/index.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,30 @@ import type {
33
Maybe,
44
MissDetail,
55
} from "@/graphql/__generated__/graphql";
6-
import ActionsPieChart, { type ActionsChartItem } from "../ActionsPieChart";
7-
import { chartColor } from "../ActionsPieChart/utils";
6+
import SummaryPieChart, { type SummaryChartItem } from "../SummaryPieChart";
87
import { nullPercent } from "../Utilities/nullPercent";
98

109
interface Props {
1110
acStatistics?: Maybe<ActionCacheStatistics>;
1211
}
1312

1413
const ActionCacheMissMetrics: React.FC<Props> = ({ acStatistics }) => {
15-
const chartItems: ActionsChartItem[] = [];
14+
const chartItems: SummaryChartItem[] = [];
1615

1716
if (acStatistics) {
1817
acStatistics?.missDetails?.forEach((item: MissDetail, index: number) => {
19-
const chartItem: ActionsChartItem = {
18+
const chartItem: SummaryChartItem = {
2019
key: index,
2120
count: item.count ?? 0,
2221
percent: nullPercent(item.count, acStatistics?.misses, 0),
23-
color: chartColor(index),
2422
value: item.reason ?? "",
2523
type: "square",
2624
};
2725
chartItems.push(chartItem);
2826
});
2927
}
3028

31-
return <ActionsPieChart items={chartItems} />;
29+
return <SummaryPieChart items={chartItems} />;
3230
};
3331

3432
export default ActionCacheMissMetrics;

frontend/src/components/ActionRunnerMetrics/index.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { RunnerCount } from "@/graphql/__generated__/graphql";
2-
import ActionsPieChart, { type ActionsChartItem } from "../ActionsPieChart";
2+
import SummaryPieChart, { type SummaryChartItem } from "../SummaryPieChart";
33
import { nullPercent } from "../Utilities/nullPercent";
44

55
interface Props {
@@ -18,12 +18,12 @@ function colorSwitchOnExecStrat(exec: string) {
1818
}
1919

2020
const ActionRunnerMetrics: React.FC<Props> = ({ runnerMetrics }) => {
21-
const chartItems: ActionsChartItem[] = [];
21+
const chartItems: SummaryChartItem[] = [];
2222
const totalCount: number =
2323
runnerMetrics.find((i) => i.name === "total")?.actionsExecuted ?? 0;
2424

2525
runnerMetrics.forEach((item: RunnerCount, index: number) => {
26-
const chartItem: ActionsChartItem = {
26+
const chartItem: SummaryChartItem = {
2727
key: index,
2828
value: item.name ?? "",
2929
count: item.actionsExecuted ?? 0,
@@ -36,7 +36,7 @@ const ActionRunnerMetrics: React.FC<Props> = ({ runnerMetrics }) => {
3636
}
3737
});
3838

39-
return <ActionsPieChart items={chartItems} />;
39+
return <SummaryPieChart items={chartItems} />;
4040
};
4141

4242
export default ActionRunnerMetrics;
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
1-
import type { ActionData } from "@/graphql/__generated__/graphql";
21
import type { Maybe } from "graphql/jsutils/Maybe";
3-
import ActionsPieChart, { type ActionsChartItem } from "../ActionsPieChart";
4-
import { chartColor } from "../ActionsPieChart/utils";
2+
import type { ActionData } from "@/graphql/__generated__/graphql";
3+
import SummaryPieChart, { type SummaryChartItem } from "../SummaryPieChart";
54
import { nullPercent } from "../Utilities/nullPercent";
65

76
interface Props {
87
actionData?: Maybe<ActionData[]>;
98
}
109

1110
const ActionTypeMetrics: React.FC<Props> = ({ actionData }) => {
12-
const actions: ActionsChartItem[] = [];
11+
const actions: SummaryChartItem[] = [];
1312
const totalActionsExecuted = actionData?.reduce(
1413
(accumulator, item) => accumulator + (item.actionsExecuted ?? 0),
1514
0,
1615
);
1716

1817
if (actionData) {
1918
actionData.forEach((item: ActionData, index: number) => {
20-
const chartItem: ActionsChartItem = {
19+
const chartItem: SummaryChartItem = {
2120
key: index,
2221
value: item.mnemonic ?? "",
2322
percent: nullPercent(item.actionsExecuted, totalActionsExecuted, 0),
2423
count: item.actionsExecuted ?? 0,
25-
color: chartColor(index),
2624
type: "square",
2725
};
2826
actions.push(chartItem);
2927
});
3028
}
3129

32-
return <ActionsPieChart items={actions} />;
30+
return <SummaryPieChart items={actions} />;
3331
};
3432

3533
export default ActionTypeMetrics;

frontend/src/components/ActionsPieChart/index.tsx

Lines changed: 0 additions & 87 deletions
This file was deleted.

frontend/src/components/ActionsPieChart/utils.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.summaryPieChartsWrapper ul li {
2+
break-inside: avoid-column;
3+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Flex, theme } from "antd";
2+
import Link from "next/link";
3+
import { useCallback, useState } from "react";
4+
import { Cell, Legend, type LegendType, Pie, PieChart } from "recharts";
5+
import type { Payload } from "recharts/types/component/DefaultLegendContent";
6+
import { renderActiveShapeCompact } from "../Utilities/renderShape";
7+
import styles from "./index.module.css";
8+
import { themeColor } from "./utils";
9+
10+
// The `Legend` component uses `value`, `color`,
11+
// and `type` by default to render the data.
12+
export interface SummaryChartItem {
13+
key: React.Key;
14+
value: string;
15+
percent: string;
16+
color?: string;
17+
count: number;
18+
type?: LegendType;
19+
href?: string;
20+
}
21+
22+
const { useToken } = theme;
23+
24+
interface Props {
25+
items: SummaryChartItem[];
26+
chartWidth?: number;
27+
}
28+
29+
const INNER_RADIUS = 30;
30+
const OUTER_RADIUS = 50;
31+
32+
const SummaryPieChart: React.FC<Props> = ({
33+
items,
34+
chartWidth = 600,
35+
}: Props) => {
36+
const { token } = useToken();
37+
38+
const renderLegendText = (value: string) => {
39+
const item = coloredItems.find((i) => i.value === value);
40+
if (value === coloredItems[activeIndexRunner].value) {
41+
return (
42+
<span>
43+
<u>
44+
<b>{item?.count ?? 0}</b>{" "}
45+
<span style={{ color: token.colorText }}>
46+
{item?.href ? <Link href={item.href}>{value}</Link> : value} (
47+
{item?.percent})
48+
</span>
49+
</u>
50+
</span>
51+
);
52+
}
53+
return (
54+
<span>
55+
<b>{item?.count ?? 0}</b>{" "}
56+
<span style={{ color: token.colorText }}>
57+
{item?.href ? <Link href={item.href}>{value}</Link> : value} (
58+
{item?.percent})
59+
</span>
60+
</span>
61+
);
62+
};
63+
64+
const [activeIndexRunner, setActiveIndexRunner] = useState<number>(0);
65+
const onRunnerPieEnter = useCallback((_: Payload, index: number) => {
66+
setActiveIndexRunner(index);
67+
}, []);
68+
69+
// Items are sorted to display the highest count first in the legend
70+
items.sort((a, b) => {
71+
return b.count - a.count;
72+
});
73+
74+
const coloredItems = items.map((item, index) => ({
75+
...item,
76+
color: item?.color || themeColor(token, index),
77+
}));
78+
79+
return (
80+
<Flex vertical gap="middle" style={{ width: chartWidth }}>
81+
<PieChart height={OUTER_RADIUS * 3} width={chartWidth}>
82+
<Pie
83+
activeIndex={activeIndexRunner}
84+
activeShape={renderActiveShapeCompact}
85+
dataKey="count"
86+
data={coloredItems}
87+
innerRadius={INNER_RADIUS}
88+
outerRadius={OUTER_RADIUS}
89+
onMouseEnter={onRunnerPieEnter}
90+
>
91+
{coloredItems.map((value: SummaryChartItem) => {
92+
return <Cell key={value.key} fill={value.color} />;
93+
})}
94+
</Pie>
95+
</PieChart>
96+
<div className={styles.summaryPieChartsWrapper}>
97+
<Legend
98+
payload={coloredItems}
99+
chartWidth={chartWidth}
100+
layout="vertical"
101+
align="center"
102+
wrapperStyle={{
103+
position: "static",
104+
columns: 2,
105+
}}
106+
formatter={renderLegendText}
107+
onMouseEnter={onRunnerPieEnter}
108+
/>
109+
</div>
110+
</Flex>
111+
);
112+
};
113+
114+
export default SummaryPieChart;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { GlobalToken } from "antd";
2+
3+
export const themeColor = (token: GlobalToken, index: number): string => {
4+
const colors: string[] = [
5+
token["lime-8"],
6+
token["purple-8"],
7+
token["red-8"],
8+
token["cyan-8"],
9+
token["yellow-8"],
10+
token["red-6"],
11+
token["blue-8"],
12+
token["magenta-8"],
13+
token["volcano-6"],
14+
token["green-8"],
15+
token["volcano-8"],
16+
token["blue-6"],
17+
token["orange-8"],
18+
token["magenta-6"],
19+
token["green-6"],
20+
token["orange-6"],
21+
token["cyan-6"],
22+
];
23+
24+
return colors[index % colors.length];
25+
};

0 commit comments

Comments
 (0)