From 1b8d70322b01f0f362b4eec69170e93002b0cda3 Mon Sep 17 00:00:00 2001 From: GyDi Date: Sat, 16 Apr 2022 17:28:30 +0800 Subject: [PATCH] feat: optimize the animation of the traffic graph --- src/components/layout/layout-traffic.tsx | 22 ++- src/components/layout/traffic-graph.tsx | 207 +++++++++++++++++++++ src/components/layout/use-traffic-graph.ts | 156 ---------------- 3 files changed, 220 insertions(+), 165 deletions(-) create mode 100644 src/components/layout/traffic-graph.tsx delete mode 100644 src/components/layout/use-traffic-graph.ts diff --git a/src/components/layout/layout-traffic.tsx b/src/components/layout/layout-traffic.tsx index 1938a66..4f0214e 100644 --- a/src/components/layout/layout-traffic.tsx +++ b/src/components/layout/layout-traffic.tsx @@ -1,5 +1,5 @@ import useSWR from "swr"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useRecoilValue } from "recoil"; import { Box, Typography } from "@mui/material"; import { ArrowDownward, ArrowUpward } from "@mui/icons-material"; @@ -8,17 +8,18 @@ import { ApiType } from "../../services/types"; import { getInfomation } from "../../services/api"; import { getVergeConfig } from "../../services/cmds"; import { atomClashPort } from "../../services/states"; +import TrafficGraph from "./traffic-graph"; import useLogSetup from "./use-log-setup"; -import useTrafficGraph from "./use-traffic-graph"; import parseTraffic from "../../utils/parse-traffic"; // setup the traffic const LayoutTraffic = () => { const portValue = useRecoilValue(atomClashPort); const [traffic, setTraffic] = useState({ up: 0, down: 0 }); - const { canvasRef, appendData, toggleStyle } = useTrafficGraph(); const [refresh, setRefresh] = useState({}); + const trafficRef = useRef(); + // whether hide traffic graph const { data } = useSWR("getVergeConfig", getVergeConfig); const trafficGraph = data?.traffic_graph ?? true; @@ -46,7 +47,7 @@ const LayoutTraffic = () => { ws.addEventListener("message", (event) => { const data = JSON.parse(event.data) as ApiType.TrafficItem; - appendData(data); + trafficRef.current?.appendData(data); setTraffic(data); }); }); @@ -72,12 +73,15 @@ const LayoutTraffic = () => { }; return ( - + {trafficGraph && ( - +
+ +
)} diff --git a/src/components/layout/traffic-graph.tsx b/src/components/layout/traffic-graph.tsx new file mode 100644 index 0000000..e347477 --- /dev/null +++ b/src/components/layout/traffic-graph.tsx @@ -0,0 +1,207 @@ +import { useEffect, useRef } from "react"; +import { useTheme } from "@mui/material"; + +const maxPoint = 30; + +const refLineAlpha = 1; +const refLineWidth = 2; + +const upLineAlpha = 0.6; +const upLineWidth = 4; + +const downLineAlpha = 1; +const downLineWidth = 4; + +const duration = 16 / 1000; +const defaultList = Array(maxPoint + 1).fill({ up: 0, down: 0 }); + +type TrafficData = { up: number; down: number }; + +interface Props { + instance: React.MutableRefObject<{ + appendData: (data: TrafficData) => void; + toggleStyle: () => void; + }>; +} + +/** + * draw the traffic graph + */ +const TrafficGraph = (props: Props) => { + const { instance } = props; + + const countRef = useRef(0); + const styleRef = useRef(true); + const listRef = useRef(defaultList); + const canvasRef = useRef(null!); + + const { palette } = useTheme(); + + useEffect(() => { + let timer: any; + let cache: TrafficData | null = null; + const zero = { up: 0, down: 0 }; + + const handleData = () => { + const data = cache ? cache : zero; + cache = null; + + const list = listRef.current; + if (list.length > maxPoint + 1) list.shift(); + list.push(data); + countRef.current = 0; + + timer = setTimeout(handleData, 1000); + }; + + instance.current = { + appendData: (data: TrafficData) => { + cache = data; + }, + toggleStyle: () => { + styleRef.current = !styleRef.current; + }, + }; + + handleData(); + + return () => { + instance.current = null!; + if (timer) clearTimeout(timer); + }; + }, []); + + useEffect(() => { + let raf = 0; + const canvas = canvasRef.current!; + + if (!canvas) return; + + const context = canvas.getContext("2d")!; + + if (!context) return; + + const { primary, secondary, divider } = palette; + const refLineColor = divider || "rgba(0, 0, 0, 0.12)"; + const upLineColor = secondary.main || "#9c27b0"; + const downLineColor = primary.main || "#5b5c9d"; + + const width = canvas.width; + const height = canvas.height; + const dx = width / maxPoint; + const dy = height / 7; + const l1 = dy; + const l2 = dy * 4; + + const countY = (v: number) => { + const h = height; + + if (v == 0) return h - 1; + if (v <= 10) return h - (v / 10) * dy; + if (v <= 100) return h - (v / 100 + 1) * dy; + if (v <= 1024) return h - (v / 1024 + 2) * dy; + if (v <= 10240) return h - (v / 10240 + 3) * dy; + if (v <= 102400) return h - (v / 102400 + 4) * dy; + if (v <= 1048576) return h - (v / 1048576 + 5) * dy; + if (v <= 10485760) return h - (v / 10485760 + 6) * dy; + return 1; + }; + + const drawBezier = (list: number[]) => { + const count = countRef.current; + const offset = Math.min(1, count * duration); + const offsetX = dx * offset; + + let lx = 0; + let ly = height; + let llx = 0; + let lly = height; + + list.forEach((val, index) => { + const x = (dx * index - offsetX) | 0; + const y = countY(val); + const s = 0.25; + + if (index === 0) context.moveTo(x, y); + else { + let nx = (dx * (index + 1)) | 0; + let ny = index < maxPoint - 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 count = countRef.current; + const offset = Math.min(1, count * duration); + const offsetX = dx * offset; + + list.forEach((val, index) => { + const x = (dx * index - offsetX) | 0; + const y = countY(val); + + if (index === 0) context.moveTo(x, y); + else context.lineTo(x, y); + }); + }; + + const drawGraph = () => { + 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 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(); + + countRef.current += 1; + + raf = requestAnimationFrame(drawGraph); + }; + + drawGraph(); + + return () => { + cancelAnimationFrame(raf); + }; + }, [palette]); + + return ; +}; + +export default TrafficGraph; diff --git a/src/components/layout/use-traffic-graph.ts b/src/components/layout/use-traffic-graph.ts deleted file mode 100644 index c723f17..0000000 --- a/src/components/layout/use-traffic-graph.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { useEffect, useRef } from "react"; -import { useTheme } from "@mui/material"; - -const minPoint = 10; -const maxPoint = 36; - -const refLineAlpha = 1; -const refLineWidth = 2; - -const upLineAlpha = 0.6; -const upLineWidth = 4; - -const downLineAlpha = 1; -const downLineWidth = 4; - -/** - * draw the traffic graph - */ -export default function useTrafficGraph() { - type TrafficData = { up: number; down: number }; - const listRef = useRef([]); - const styleRef = useRef(true); - const canvasRef = useRef(null!); - - const { palette } = useTheme(); - const paletteRef = useRef(palette); - - useEffect(() => { - paletteRef.current = palette; - }, [palette]); - - const drawGraph = () => { - const canvas = canvasRef.current!; - - if (!canvas) return; - - const { primary, secondary, divider } = paletteRef.current; - const refLineColor = divider || "rgba(0, 0, 0, 0.12)"; - const upLineColor = secondary.main || "#9c27b0"; - const downLineColor = primary.main || "#5b5c9d"; - - 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, - }; -}