diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx
index e2da3300046a335e5e5b42c6e9a632fee898813e..8e16f35d7d56866a2240f67f09377c529bccb650 100644
--- a/src/components/layout/layout-traffic.tsx
+++ b/src/components/layout/layout-traffic.tsx
@@ -6,10 +6,12 @@ import { getInfomation } from "../../services/api";
 import { ApiType } from "../../services/types";
 import { atomClashPort } from "../../states/setting";
 import parseTraffic from "../../utils/parse-traffic";
+import useTrafficGraph from "./use-traffic-graph";
 
 const LayoutTraffic = () => {
   const portValue = useRecoilValue(atomClashPort);
   const [traffic, setTraffic] = useState({ up: 0, down: 0 });
+  const { canvasRef, appendData, toggleStyle } = useTrafficGraph();
 
   useEffect(() => {
     let ws: WebSocket | null = null;
@@ -19,7 +21,9 @@ const LayoutTraffic = () => {
       ws = new WebSocket(`ws://${server}/traffic?token=${secret}`);
 
       ws.addEventListener("message", (event) => {
-        setTraffic(JSON.parse(event.data) as ApiType.TrafficItem);
+        const data = JSON.parse(event.data) as ApiType.TrafficItem;
+        appendData(data);
+        setTraffic(data);
       });
     });
 
@@ -44,7 +48,12 @@ const LayoutTraffic = () => {
   };
 
   return (
-    <Box width="110px">
+    <Box data-windrag width="110px" position="relative" onClick={toggleStyle}>
+      <canvas
+        ref={canvasRef}
+        style={{ width: "100%", height: 60, marginBottom: 6 }}
+      />
+
       <Box mb={1.5} display="flex" alignItems="center" whiteSpace="nowrap">
         <ArrowUpward
           fontSize="small"
diff --git a/src/components/layout/use-traffic-graph.ts b/src/components/layout/use-traffic-graph.ts
new file mode 100644
index 0000000000000000000000000000000000000000..187851d9f0838f187ebb06e6d02a1a9d8dac37b3
--- /dev/null
+++ b/src/components/layout/use-traffic-graph.ts
@@ -0,0 +1,143 @@
+import { useRef } from "react";
+
+const minPoint = 10;
+const maxPoint = 36;
+
+const refLineAlpha = 0.5;
+const refLineWidth = 2;
+const refLineColor = "#ccc";
+
+const upLineAlpha = 0.6;
+const upLineWidth = 4;
+const upLineColor = "#9c27b0";
+
+const downLineAlpha = 1;
+const downLineWidth = 4;
+const downLineColor = "#5b5c9d";
+
+/**
+ * draw the traffic graph
+ */
+export default function useTrafficGraph() {
+  type TrafficData = { up: number; down: number };
+  const listRef = useRef<TrafficData[]>([]);
+  const styleRef = useRef(true);
+  const canvasRef = useRef<HTMLCanvasElement>(null!);
+
+  const drawGraph = () => {
+    const canvas = canvasRef.current!;
+    const context = canvas.getContext("2d")!;
+    const width = canvas.width;
+    const height = canvas.height;
+    const l1 = height * 0.2;
+    const l2 = height * 0.6;
+    const dl = height * 0.4;
+
+    context.clearRect(0, 0, width, height);
+
+    // Reference lines
+    context.beginPath();
+    context.globalAlpha = refLineAlpha;
+    context.lineWidth = refLineWidth;
+    context.strokeStyle = refLineColor;
+    context.moveTo(0, l1);
+    context.lineTo(width, l1);
+    context.moveTo(0, l2);
+    context.lineTo(width, l2);
+    context.stroke();
+    context.closePath();
+
+    const countY = (value: number) => {
+      let v = value;
+      if (v < 1024) v = (v / 1024) * dl;
+      else if (v < 1048576) v = dl + (v / 1048576) * dl;
+      else v = 2 * dl + (v / 10485760) * l1;
+      return height - v;
+    };
+
+    const drawBezier = (list: number[]) => {
+      const len = list.length;
+      const size = Math.min(Math.max(len, minPoint), maxPoint);
+      const axis = width / size;
+
+      let lx = 0;
+      let ly = height;
+      let llx = 0;
+      let lly = height;
+
+      list.forEach((val, index) => {
+        const x = (axis * index) | 0;
+        const y = countY(val);
+        const s = 0.25;
+
+        if (index === 0) context.moveTo(x, y);
+        else {
+          let nx = (axis * (index + 1)) | 0;
+          let ny = index < len - 1 ? countY(list[index + 1]) | 0 : 0;
+          const ax = (lx + (x - llx) * s) | 0;
+          const ay = (ly + (y - lly) * s) | 0;
+          const bx = (x - (nx - lx) * s) | 0;
+          const by = (y - (ny - ly) * s) | 0;
+          context.bezierCurveTo(ax, ay, bx, by, x, y);
+        }
+
+        llx = lx;
+        lly = ly;
+        lx = x;
+        ly = y;
+      });
+    };
+
+    const drawLine = (list: number[]) => {
+      const len = list.length;
+      const size = Math.min(Math.max(len, minPoint), maxPoint);
+      const axis = width / size;
+
+      list.forEach((val, index) => {
+        const x = (axis * index) | 0;
+        const y = countY(val);
+
+        if (index === 0) context.moveTo(x, y);
+        else context.lineTo(x, y);
+      });
+    };
+
+    const listUp = listRef.current.map((v) => v.up);
+    const listDown = listRef.current.map((v) => v.down);
+    const lineStyle = styleRef.current;
+
+    context.beginPath();
+    context.globalAlpha = upLineAlpha;
+    context.lineWidth = upLineWidth;
+    context.strokeStyle = upLineColor;
+    lineStyle ? drawLine(listUp) : drawBezier(listUp);
+    context.stroke();
+    context.closePath();
+
+    context.beginPath();
+    context.globalAlpha = downLineAlpha;
+    context.lineWidth = downLineWidth;
+    context.strokeStyle = downLineColor;
+    lineStyle ? drawLine(listDown) : drawBezier(listDown);
+    context.stroke();
+    context.closePath();
+  };
+
+  const appendData = (data: TrafficData) => {
+    const list = listRef.current;
+    if (list.length > maxPoint) list.shift();
+    list.push(data);
+    drawGraph();
+  };
+
+  const toggleStyle = () => {
+    styleRef.current = !styleRef.current;
+    drawGraph();
+  };
+
+  return {
+    canvasRef,
+    appendData,
+    toggleStyle,
+  };
+}