feat: optimize the animation of the traffic graph
This commit is contained in:
parent
844ffab4ed
commit
1b8d70322b
@ -1,5 +1,5 @@
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useRecoilValue } from "recoil";
|
import { useRecoilValue } from "recoil";
|
||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
import { ArrowDownward, ArrowUpward } from "@mui/icons-material";
|
import { ArrowDownward, ArrowUpward } from "@mui/icons-material";
|
||||||
@ -8,17 +8,18 @@ import { ApiType } from "../../services/types";
|
|||||||
import { getInfomation } from "../../services/api";
|
import { getInfomation } from "../../services/api";
|
||||||
import { getVergeConfig } from "../../services/cmds";
|
import { getVergeConfig } from "../../services/cmds";
|
||||||
import { atomClashPort } from "../../services/states";
|
import { atomClashPort } from "../../services/states";
|
||||||
|
import TrafficGraph from "./traffic-graph";
|
||||||
import useLogSetup from "./use-log-setup";
|
import useLogSetup from "./use-log-setup";
|
||||||
import useTrafficGraph from "./use-traffic-graph";
|
|
||||||
import parseTraffic from "../../utils/parse-traffic";
|
import parseTraffic from "../../utils/parse-traffic";
|
||||||
|
|
||||||
// setup the traffic
|
// setup the traffic
|
||||||
const LayoutTraffic = () => {
|
const LayoutTraffic = () => {
|
||||||
const portValue = useRecoilValue(atomClashPort);
|
const portValue = useRecoilValue(atomClashPort);
|
||||||
const [traffic, setTraffic] = useState({ up: 0, down: 0 });
|
const [traffic, setTraffic] = useState({ up: 0, down: 0 });
|
||||||
const { canvasRef, appendData, toggleStyle } = useTrafficGraph();
|
|
||||||
const [refresh, setRefresh] = useState({});
|
const [refresh, setRefresh] = useState({});
|
||||||
|
|
||||||
|
const trafficRef = useRef<any>();
|
||||||
|
|
||||||
// whether hide traffic graph
|
// whether hide traffic graph
|
||||||
const { data } = useSWR("getVergeConfig", getVergeConfig);
|
const { data } = useSWR("getVergeConfig", getVergeConfig);
|
||||||
const trafficGraph = data?.traffic_graph ?? true;
|
const trafficGraph = data?.traffic_graph ?? true;
|
||||||
@ -46,7 +47,7 @@ const LayoutTraffic = () => {
|
|||||||
|
|
||||||
ws.addEventListener("message", (event) => {
|
ws.addEventListener("message", (event) => {
|
||||||
const data = JSON.parse(event.data) as ApiType.TrafficItem;
|
const data = JSON.parse(event.data) as ApiType.TrafficItem;
|
||||||
appendData(data);
|
trafficRef.current?.appendData(data);
|
||||||
setTraffic(data);
|
setTraffic(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -72,12 +73,15 @@ const LayoutTraffic = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box width="110px" position="relative" onClick={toggleStyle}>
|
<Box
|
||||||
|
width="110px"
|
||||||
|
position="relative"
|
||||||
|
onClick={trafficRef.current?.toggleStyle}
|
||||||
|
>
|
||||||
{trafficGraph && (
|
{trafficGraph && (
|
||||||
<canvas
|
<div style={{ width: "100%", height: 60, marginBottom: 6 }}>
|
||||||
ref={canvasRef}
|
<TrafficGraph instance={trafficRef} />
|
||||||
style={{ width: "100%", height: 60, marginBottom: 6 }}
|
</div>
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box mb={1.5} display="flex" alignItems="center" whiteSpace="nowrap">
|
<Box mb={1.5} display="flex" alignItems="center" whiteSpace="nowrap">
|
||||||
|
207
src/components/layout/traffic-graph.tsx
Normal file
207
src/components/layout/traffic-graph.tsx
Normal file
@ -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<TrafficData[]>(defaultList);
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(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 <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TrafficGraph;
|
@ -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<TrafficData[]>([]);
|
|
||||||
const styleRef = useRef(true);
|
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(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,
|
|
||||||
};
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user