From a4cbbf30ef3c62f23e15d4cf1410bf5f3e940d40 Mon Sep 17 00:00:00 2001 From: yuanyxh <15766118362@139.com> Date: Tue, 24 Sep 2024 22:09:32 +0800 Subject: [PATCH] feat: complete pie chart component --- src/components/Canvas/Canvas.tsx | 93 ---------------------------- src/components/PieChart/PieChart.tsx | 55 ++++++++++++++++ src/components/index.ts | 4 +- src/utils/canvas.ts | 22 +++++++ src/utils/index.ts | 1 + src/viewer/Settings.tsx | 52 ++++++---------- 6 files changed, 98 insertions(+), 129 deletions(-) delete mode 100644 src/components/Canvas/Canvas.tsx create mode 100644 src/components/PieChart/PieChart.tsx create mode 100644 src/utils/canvas.ts diff --git a/src/components/Canvas/Canvas.tsx b/src/components/Canvas/Canvas.tsx deleted file mode 100644 index a24649b..0000000 --- a/src/components/Canvas/Canvas.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { forwardRef, memo, useEffect, useImperativeHandle, useRef } from 'react'; - -interface ICanvasProps extends React.CanvasHTMLAttributes {} - -type ExposeKeys = 'beginPath' | 'moveTo' | 'arc' | 'fillText' | 'stroke' | 'lineTo'; - -export interface CanvasInstance extends Pick { - fill(fillRule?: CanvasFillRule): void; - lineWidth(lineWidth?: number): number; - fillStyle( - fillStyle?: string | CanvasGradient | CanvasPattern - ): string | CanvasGradient | CanvasPattern; - strokeStyle( - strokeStyle?: string | CanvasGradient | CanvasPattern - ): string | CanvasGradient | CanvasPattern; - font(font?: string): string; - textAlign(textAlign?: CanvasTextAlign): CanvasTextAlign; - width(width?: number): number; - height(height?: number): number; -} - -export default memo( - forwardRef(function Canvas(props, ref) { - const { ...rest } = props; - - const canvasRef = useRef(null); - const ctxRef = useRef(); - - useEffect(() => { - ctxRef.current = canvasRef.current!.getContext('2d')!; - - const w = canvasRef.current!.width; - const h = canvasRef.current!.height; - - const ratio = window.devicePixelRatio || 1; - canvasRef.current!.width = w * ratio; - canvasRef.current!.height = h * ratio; - canvasRef.current!.style.width = `${w}px`; - canvasRef.current!.style.height = `${h}px`; - - ctxRef.current.imageSmoothingEnabled = true; - ctxRef.current.imageSmoothingQuality = 'high'; - ctxRef.current.scale(ratio, ratio); - }, []); - - useImperativeHandle(ref, () => ({ - beginPath: (...args) => ctxRef.current!.beginPath(...args), - moveTo: (...args) => ctxRef.current!.moveTo(...args), - arc: (...args) => ctxRef.current!.arc(...args), - fill: (fillRule?: CanvasFillRule) => ctxRef.current!.fill(fillRule), - fillText: (...args) => ctxRef.current!.fillText(...args), - stroke: () => ctxRef.current!.stroke(), - strokeStyle: (strokeStyle) => { - if (strokeStyle === undefined) return ctxRef.current!.strokeStyle; - ctxRef.current!.strokeStyle = strokeStyle; - return ctxRef.current!.strokeStyle; - }, - lineTo: (...args) => ctxRef.current!.lineTo(...args), - lineWidth: (lineWidth) => { - if (lineWidth === undefined) return ctxRef.current!.lineWidth; - ctxRef.current!.lineWidth = lineWidth; - return ctxRef.current!.lineWidth; - }, - fillStyle: (fillStyle) => { - if (fillStyle === undefined) return ctxRef.current!.fillStyle; - ctxRef.current!.fillStyle = fillStyle; - return ctxRef.current!.fillStyle; - }, - font: (font) => { - if (font === undefined) return ctxRef.current!.font; - ctxRef.current!.font = font; - return ctxRef.current!.font; - }, - textAlign: (textAlign) => { - if (textAlign === undefined) return ctxRef.current!.textAlign; - ctxRef.current!.textAlign = textAlign; - return ctxRef.current!.textAlign; - }, - width(width) { - if (width === undefined) return canvasRef.current!.width; - canvasRef.current!.width = width; - return canvasRef.current!.width; - }, - height(height) { - if (height === undefined) return canvasRef.current!.height; - canvasRef.current!.height = height; - return canvasRef.current!.height; - } - })); - - return ; - }) -); diff --git a/src/components/PieChart/PieChart.tsx b/src/components/PieChart/PieChart.tsx new file mode 100644 index 0000000..a25febc --- /dev/null +++ b/src/components/PieChart/PieChart.tsx @@ -0,0 +1,55 @@ +import { useEffect, useRef } from 'react'; + +import { clearCanvas, scaleCanvas } from '@/utils'; + +export interface PieItemConfig { + value: number; + color: string; +} + +export interface IPieChartProps { + radius: number; + data: PieItemConfig[]; +} + +const PieChart: React.FC> = (props) => { + const { radius, data } = props; + + const canvasRef = useRef(null); + const ctxRef = useRef(); + + useEffect(() => { + if (!ctxRef.current) { + ctxRef.current = scaleCanvas(canvasRef.current!); + + clearCanvas(ctxRef.current); + } + + const ctx = ctxRef.current; + + const circle = Math.PI * 2; + + const total = data.reduce((p, c) => p + c.value, 0); + + let ratio = 0; + for (let i = 0; i < data.length; i++) { + const sector = data[i]; + + const nextRatio = circle * (sector.value / total || 0); + + ctx.beginPath(); + ctx.moveTo(radius, radius); + ctx.arc(radius, radius, radius, ratio, nextRatio); + ctx.fillStyle = sector.color; + ctx.fill(); + + ratio = nextRatio; + } + }, [radius, data]); + + const size = radius * 2; + + return ; +}; + +export default PieChart; diff --git a/src/components/index.ts b/src/components/index.ts index 97f7337..2772030 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,8 +1,8 @@ -export { default as Canvas } from './Canvas/Canvas'; -export * from './Canvas/Canvas'; export { default as ContextMenu } from './ContextMenu/ContextMenu'; export { default as Dialog } from './Dialog/Dialog'; export * from './Dialog/Dialog'; export { default as Icon } from './Icon/Icon'; export { default as Image } from './Image/Image'; export { default as Loading } from './Loading/Loading'; +export { default as PieChart } from './PieChart/PieChart'; +export * from './PieChart/PieChart'; diff --git a/src/utils/canvas.ts b/src/utils/canvas.ts new file mode 100644 index 0000000..15dc02e --- /dev/null +++ b/src/utils/canvas.ts @@ -0,0 +1,22 @@ +export const scaleCanvas = (canvas: HTMLCanvasElement) => { + const ctx = canvas.getContext('2d')!; + + const w = canvas.width; + const h = canvas.height; + + const ratio = window.devicePixelRatio || 1; + canvas.width = w * ratio; + canvas.height = h * ratio; + canvas.style.width = `${w}px`; + canvas.style.height = `${h}px`; + + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = 'high'; + ctx.scale(ratio, ratio); + + return ctx; +}; + +export const clearCanvas = (ctx: CanvasRenderingContext2D) => { + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index f897a0d..bba272d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,4 @@ +export * from './canvas.ts'; export * from './error.ts'; export { default as EventEmitter } from './event'; export * from './fullscreen'; diff --git a/src/viewer/Settings.tsx b/src/viewer/Settings.tsx index af67cf4..5c1c5bd 100644 --- a/src/viewer/Settings.tsx +++ b/src/viewer/Settings.tsx @@ -14,8 +14,7 @@ import { import { getStorageUsage } from '@/filehandle/utils/index'; -import type { CanvasInstance } from '@/components'; -import { Canvas } from '@/components'; +import { PieChart } from '@/components'; import styles from './styles/Settings.module.less'; @@ -217,8 +216,6 @@ function PersistentStorage() { } function StorageDetail() { - const drawRef = useRef(null); - const [storage, setStorage] = useState<{ quota: number; usage: number; @@ -244,30 +241,6 @@ function StorageDetail() { usage: usage / 1000 / 1000, unUsage: unUsage / 1000 / 1000 }); - - const usageAngle = Math.PI * 2 * (res.usage / res.quota); - const unUsageAngle = Math.PI * 2 * ((res.quota - res.usage) / res.quota); - - const drawEl = drawRef.current!; - - const centerX = drawEl.width() / 2; - const centerY = drawEl.height() / 2; - - drawEl.arc(centerX, centerY, centerX - 2, 0, Math.PI * 2); - drawEl.strokeStyle('#f0f0f0'); - drawEl.stroke(); - - drawEl.beginPath(); - drawEl.moveTo(centerX, centerY); - drawEl.arc(centerX, centerY, centerX - 2, 0, usageAngle); - drawEl.fillStyle('#ff9759'); - drawEl.fill(); - - drawEl.beginPath(); - drawEl.moveTo(centerX, centerY); - drawEl.arc(centerX, centerY, centerX - 2, usageAngle, usageAngle + unUsageAngle); - drawEl.fillStyle('white'); - drawEl.fill(); }) .catch((err) => { error((err as Error).message); @@ -280,8 +253,19 @@ function StorageDetail() {
网站存储详情
- {/* TODO: remove this component and add Pie chart */} - +
@@ -293,17 +277,17 @@ function StorageDetail() {
- + - 已用存储 + 已用存储量 {storage.usage.toFixed(3)} MB
- + - 剩余可用 + 剩余可用量 {storage.unUsage.toFixed(3)} MB