Skip to content

Commit

Permalink
Change: Doughnut Chart Design
Browse files Browse the repository at this point in the history
  • Loading branch information
shm11C3 committed Dec 15, 2024
1 parent 9ea5188 commit 1ac2c58
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 71 deletions.
156 changes: 97 additions & 59 deletions src/components/charts/DoughnutChart.tsx
Original file line number Diff line number Diff line change
@@ -1,82 +1,120 @@
import { useSettingsAtom } from "@/atom/useSettingsAtom";
import { type ChartConfig, ChartContainer } from "@/components/ui/chart";
import { Skeleton } from "@/components/ui/skeleton";
import type { HardwareDataType } from "@/types/hardwareDataType";
import { Lightning, Speedometer, Thermometer } from "@phosphor-icons/react";
import {
ArcElement,
Chart as ChartJS,
type ChartOptions,
Legend,
Tooltip,
} from "chart.js";
import { Doughnut } from "react-chartjs-2";
import { useTranslation } from "react-i18next";
import {
Label,
PolarGrid,
PolarRadiusAxis,
RadialBar,
RadialBarChart,
} from "recharts";

ChartJS.register(ArcElement, Tooltip, Legend);

const DoughnutChart = ({
chartData,
export const DoughnutChart = ({
chartValue,
dataType,
}: {
chartData: number;
chartValue: number;
dataType: HardwareDataType;
}) => {
const { settings } = useSettingsAtom();

const { t } = useTranslation();

const displayDataType: Record<HardwareDataType, string> = {
usage: t("shared.usage"),
temp: t("shared.temperature"),
clock: t("shared.clock"),
};

const data = {
datasets: [
{
data: [chartData, 100 - chartData],
backgroundColor: {
light: ["#374151", "#f3f4f6"],
dark: ["#888", "#222"],
}[settings.theme],
borderWidth: 0,
},
],
};

const options: ChartOptions<"doughnut"> = {
cutout: "85%",
plugins: {
tooltip: { enabled: false },
const chartConfig: Record<
HardwareDataType,
{ label: string; color: string }
> = {
usage: {
label: t("shared.usage"),
color: "hsl(var(--chart-2))",
},
};
temp: {
label: t("shared.temperature"),
color: "hsl(var(--chart-3))",
},
clock: {
label: t("shared.clock"),
color: "hsl(var(--chart-4))",
},
} satisfies ChartConfig;

const chartData = [
{ type: dataType, value: chartValue, fill: `var(--color-${dataType})` },
];

const dataTypeIcons: Record<HardwareDataType, JSX.Element> = {
usage: <Lightning className="mr-1" size={18} weight="duotone" />,
temp: <Thermometer className="mr-1" size={18} weight="duotone" />,
clock: <Speedometer className="mr-1" size={18} weight="duotone" />,
usage: <Lightning className="mr-1" size={12} weight="duotone" />,
temp: <Thermometer className="mr-1" size={12} weight="duotone" />,
clock: <Speedometer className="mr-1" size={12} weight="duotone" />,
};

const dataTypeUnits: Record<HardwareDataType, string> = {
usage: "%",
temp: "",
temp: "°C",
clock: "MHz",
};

return (
<div className="p-2 w-36 relative">
<Doughnut data={data} options={options} />
<div className="absolute inset-x-0 flex flex-col items-center justify-center top-16">
<span className="text-slate-800 dark:text-white text-xl font-semibold">
{chartData}
{dataTypeUnits[dataType]}
</span>
</div>
<span className="flex justify-center my-4 text-gray-800 dark:text-gray-400">
{dataTypeIcons[dataType]}
{displayDataType[dataType]}
</span>
</div>
<ChartContainer
config={chartConfig}
className="aspect-square max-h-[200px]"
>
{chartData[0].value != null ? (
<RadialBarChart
data={chartData}
startAngle={0}
endAngle={chartValue * 3.6}
innerRadius={50}
outerRadius={70}
>
<PolarGrid
gridType="circle"
radialLines={false}
stroke="none"
className="first:fill-zinc-300 dark:first:fill-muted last:fill-zinc-200 last:dark:fill-gray-900"
polarRadius={[70, 60]}
/>
<RadialBar dataKey="value" background cornerRadius={10} />
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
<Label
content={({ viewBox }) => {
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
return (
<g>
{/* メインの値表示 */}
<text
x={viewBox.cx}
y={viewBox.cy}
textAnchor="middle"
dominantBaseline="middle"
className="fill-foreground text-2xl font-bold"
>
{`${chartValue}${dataTypeUnits[dataType]}`}
</text>
{/* ラベルとアイコンの表示 */}
<foreignObject
x={(viewBox.cx || 0) - 42}
y={(viewBox.cy || 0) + 20}
width="80"
height="40"
>
<div className="flex items-center justify-center dark:text-muted-foreground ">
{dataTypeIcons[dataType]}
<span>{chartConfig[dataType].label}</span>
</div>
</foreignObject>
</g>
);
}
}}
/>
</PolarRadiusAxis>
</RadialBarChart>
) : (
<div className="flex items-center justify-center h-full">
<Skeleton className="w-32 h-32 rounded-full" />
</div>
)}
</ChartContainer>
);
};

export { DoughnutChart };
15 changes: 15 additions & 0 deletions src/components/ui/skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { cn } from "@/lib/utils"

function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-neutral-100 dark:bg-neutral-800", className)}
{...props}
/>
)
}

export { Skeleton }
18 changes: 6 additions & 12 deletions src/template/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
cpuUsageHistoryAtom,
gpuFanSpeedAtom,
gpuTempAtom,
graphicUsageHistoryAtom,
memoryUsageHistoryAtom,
Expand All @@ -20,7 +19,7 @@ import { useTranslation } from "react-i18next";

const InfoTable = ({ data }: { data: { [key: string]: string | number } }) => {
return (
<div className="p-4 border rounded-md shadow-md bg-zinc-300 dark:bg-gray-800 dark:text-white">
<div className="px-4 pt-2 pb-4 border rounded-md shadow-md bg-zinc-300 dark:bg-gray-800 dark:text-white">
<table className="w-full text-left">
<tbody>
{Object.keys(data).map((key) => (
Expand Down Expand Up @@ -65,7 +64,7 @@ const CPUInfo = () => {
hardwareInfo.cpu && (
<>
<DoughnutChart
chartData={cpuUsageHistory[cpuUsageHistory.length - 1]}
chartValue={cpuUsageHistory[cpuUsageHistory.length - 1]}
dataType={"usage"}
/>
<InfoTable
Expand All @@ -86,7 +85,6 @@ const GPUInfo = () => {
const { t } = useTranslation();
const [graphicUsageHistory] = useAtom(graphicUsageHistoryAtom);
const [gpuTemp] = useAtom(gpuTempAtom);
const [gpuFan] = useAtom(gpuFanSpeedAtom);
const { hardwareInfo } = useHardwareInfoAtom();

const getTargetInfo = (data: NameValues) => {
Expand All @@ -96,21 +94,17 @@ const GPUInfo = () => {
};

const targetTemperature = getTargetInfo(gpuTemp);
const targetFanSpeed = getTargetInfo(gpuFan);

return (
hardwareInfo.gpus && (
<>
<div className="flex justify-around">
<div className="flex justify-around h-[200px]">
<DoughnutChart
chartData={graphicUsageHistory[graphicUsageHistory.length - 1]}
chartValue={graphicUsageHistory[graphicUsageHistory.length - 1]}
dataType={"usage"}
/>
{targetTemperature && (
<DoughnutChart chartData={targetTemperature} dataType={"temp"} />
)}
{targetFanSpeed && (
<DoughnutChart chartData={targetFanSpeed} dataType={"clock"} />
<DoughnutChart chartValue={targetTemperature} dataType={"temp"} />
)}
</div>

Expand Down Expand Up @@ -140,7 +134,7 @@ const MemoryInfo = () => {
hardwareInfo.memory && (
<>
<DoughnutChart
chartData={memoryUsageHistory[memoryUsageHistory.length - 1]}
chartValue={memoryUsageHistory[memoryUsageHistory.length - 1]}
dataType={"usage"}
/>
<InfoTable
Expand Down

0 comments on commit 1ac2c58

Please sign in to comment.