1
- import React , { useEffect , useRef } from 'react' ;
1
+ import React , { useEffect , useRef , useState } from 'react' ;
2
2
import * as d3 from 'd3' ;
3
3
import { AnimationState } from '../state/animationSlice' ;
4
4
@@ -22,6 +22,29 @@ interface CanvasComponentProps {
22
22
23
23
const CanvasComponent : React . FC < CanvasComponentProps > = ( { state, width, height } ) => {
24
24
const svgRef = useRef < SVGSVGElement > ( null ) ;
25
+ const [ dimensions , setDimensions ] = useState ( { width, height } ) ;
26
+
27
+ // 监听窗口尺寸变化
28
+ useEffect ( ( ) => {
29
+ const handleResize = ( ) => {
30
+ if ( svgRef . current ) {
31
+ const newWidth = window . innerWidth * 0.65 ;
32
+ const newHeight = window . innerHeight * 0.7 ;
33
+ setDimensions ( { width : newWidth , height : newHeight } ) ;
34
+ }
35
+ } ;
36
+
37
+ window . addEventListener ( 'resize' , handleResize ) ;
38
+
39
+ return ( ) => {
40
+ window . removeEventListener ( 'resize' , handleResize ) ;
41
+ } ;
42
+ } , [ ] ) ;
43
+
44
+ // 更新尺寸
45
+ useEffect ( ( ) => {
46
+ setDimensions ( { width, height } ) ;
47
+ } , [ width , height ] ) ;
25
48
26
49
// 渲染楼梯节点图
27
50
useEffect ( ( ) => {
@@ -30,34 +53,54 @@ const CanvasComponent: React.FC<CanvasComponentProps> = ({ state, width, height
30
53
const svg = d3 . select ( svgRef . current ) ;
31
54
svg . selectAll ( '*' ) . remove ( ) ; // 清空画布
32
55
56
+ // 设置预留边距
57
+ const margin = { top : 30 , right : 30 , bottom : 30 , left : 30 } ;
58
+ const innerWidth = dimensions . width - margin . left - margin . right ;
59
+ const innerHeight = dimensions . height - margin . top - margin . bottom ;
60
+
61
+ // 创建根容器
62
+ const g = svg . append ( 'g' )
63
+ . attr ( 'transform' , `translate(${ margin . left } , ${ margin . top } )` ) ;
64
+
33
65
// 创建图层
34
- const nodeGroup = svg . append ( 'g' ) . attr ( 'class' , 'nodes' ) ;
35
- const linkGroup = svg . append ( 'g' ) . attr ( 'class' , 'links' ) ;
36
- const textGroup = svg . append ( 'g' ) . attr ( 'class' , 'texts' ) ;
37
- const formulaGroup = svg . append ( 'g' ) . attr ( 'class' , 'formula' ) ;
66
+ const nodeGroup = g . append ( 'g' ) . attr ( 'class' , 'nodes' ) ;
67
+ const linkGroup = g . append ( 'g' ) . attr ( 'class' , 'links' ) ;
68
+ const textGroup = g . append ( 'g' ) . attr ( 'class' , 'texts' ) ;
69
+ const formulaGroup = g . append ( 'g' ) . attr ( 'class' , 'formula' ) ;
70
+
71
+ // 计算节点的缩放比例
72
+ const maxX = Math . max ( ...state . staircase . nodes . map ( n => n . x ) ) || 100 ;
73
+ const maxY = Math . max ( ...state . staircase . nodes . map ( n => n . y ) ) || 100 ;
74
+ const scaleX = innerWidth / ( maxX + 100 ) ;
75
+ const scaleY = innerHeight / ( maxY + 60 ) ;
76
+
77
+ // 计算节点位置的映射函数
78
+ const mapX = ( x : number ) => x * scaleX ;
79
+ const mapY = ( y : number ) => y * scaleY ;
38
80
39
81
// 绘制连接线
40
82
linkGroup . selectAll ( 'line' )
41
83
. data ( state . staircase . links )
42
84
. enter ( )
43
85
. append ( 'line' )
44
- . attr ( 'x1' , ( d : LinkData ) => state . staircase . nodes [ d . source ] . x )
45
- . attr ( 'y1' , ( d : LinkData ) => state . staircase . nodes [ d . source ] . y )
46
- . attr ( 'x2' , ( d : LinkData ) => state . staircase . nodes [ d . target ] . x )
47
- . attr ( 'y2' , ( d : LinkData ) => state . staircase . nodes [ d . target ] . y )
86
+ . attr ( 'x1' , ( d : LinkData ) => mapX ( state . staircase . nodes [ d . source ] . x ) )
87
+ . attr ( 'y1' , ( d : LinkData ) => mapY ( state . staircase . nodes [ d . source ] . y ) )
88
+ . attr ( 'x2' , ( d : LinkData ) => mapX ( state . staircase . nodes [ d . target ] . x ) )
89
+ . attr ( 'y2' , ( d : LinkData ) => mapY ( state . staircase . nodes [ d . target ] . y ) )
48
90
. attr ( 'stroke' , '#666' )
49
91
. attr ( 'stroke-width' , 2 ) ;
50
92
51
93
// 绘制节点
52
94
const nodeColor = getColorByAlgorithm ( state . currentAlgorithm ) ;
95
+ const nodeRadius = Math . min ( 20 , Math . max ( 15 , Math . min ( innerWidth , innerHeight ) / 30 ) ) ;
53
96
54
97
nodeGroup . selectAll ( 'circle' )
55
98
. data ( state . staircase . nodes )
56
99
. enter ( )
57
100
. append ( 'circle' )
58
- . attr ( 'cx' , ( d : NodeData ) => d . x )
59
- . attr ( 'cy' , ( d : NodeData ) => d . y )
60
- . attr ( 'r' , 20 )
101
+ . attr ( 'cx' , ( d : NodeData ) => mapX ( d . x ) )
102
+ . attr ( 'cy' , ( d : NodeData ) => mapY ( d . y ) )
103
+ . attr ( 'r' , nodeRadius )
61
104
. attr ( 'fill' , nodeColor )
62
105
. attr ( 'stroke' , '#333' )
63
106
. attr ( 'stroke-width' , 2 ) ;
@@ -67,8 +110,8 @@ const CanvasComponent: React.FC<CanvasComponentProps> = ({ state, width, height
67
110
. data ( state . staircase . nodes )
68
111
. enter ( )
69
112
. append ( 'text' )
70
- . attr ( 'x' , ( d : NodeData ) => d . x )
71
- . attr ( 'y' , ( d : NodeData ) => d . y + 5 )
113
+ . attr ( 'x' , ( d : NodeData ) => mapX ( d . x ) )
114
+ . attr ( 'y' , ( d : NodeData ) => mapY ( d . y ) + 5 )
72
115
. attr ( 'text-anchor' , 'middle' )
73
116
. attr ( 'fill' , '#fff' )
74
117
. attr ( 'font-weight' , 'bold' )
@@ -77,20 +120,20 @@ const CanvasComponent: React.FC<CanvasComponentProps> = ({ state, width, height
77
120
// 渲染公式
78
121
if ( state . formula ) {
79
122
formulaGroup . append ( 'text' )
80
- . attr ( 'x' , width - 50 )
81
- . attr ( 'y' , 30 )
123
+ . attr ( 'x' , innerWidth - 20 )
124
+ . attr ( 'y' , 20 )
82
125
. attr ( 'text-anchor' , 'end' )
83
126
. attr ( 'font-family' , 'serif' )
84
- . attr ( 'font-size' , '14px' )
127
+ . attr ( 'font-size' , ` ${ Math . min ( 14 , Math . max ( 10 , innerWidth / 40 ) ) } px` )
85
128
. text ( state . formula ) ;
86
129
}
87
130
88
131
// 渲染矩阵(仅当使用矩阵算法时)
89
132
if ( state . currentAlgorithm === 'matrix' && state . matrix . length > 0 ) {
90
- renderMatrix ( svg , state . matrix ) ;
133
+ renderMatrix ( g , state . matrix , innerWidth , innerHeight ) ;
91
134
}
92
135
93
- } , [ state . staircase , state . currentAlgorithm , state . formula , state . matrix , width , height ] ) ;
136
+ } , [ state . staircase , state . currentAlgorithm , state . formula , state . matrix , dimensions ] ) ;
94
137
95
138
// 根据算法类型获取颜色
96
139
const getColorByAlgorithm = ( algorithm : AnimationState [ 'currentAlgorithm' ] ) : string => {
@@ -107,12 +150,17 @@ const CanvasComponent: React.FC<CanvasComponentProps> = ({ state, width, height
107
150
} ;
108
151
109
152
// 渲染矩阵
110
- const renderMatrix = ( svg : d3 . Selection < SVGSVGElement , unknown , null , undefined > , matrix : number [ ] [ ] ) => {
111
- const matrixGroup = svg . append ( 'g' )
153
+ const renderMatrix = (
154
+ g : d3 . Selection < SVGGElement , unknown , null , undefined > ,
155
+ matrix : number [ ] [ ] ,
156
+ width : number ,
157
+ height : number
158
+ ) => {
159
+ const matrixGroup = g . append ( 'g' )
112
160
. attr ( 'class' , 'matrix' )
113
161
. attr ( 'transform' , `translate(${ width - 100 } , 50)` ) ;
114
162
115
- const cellSize = 30 ;
163
+ const cellSize = Math . min ( 30 , Math . max ( 20 , width / 20 ) ) ;
116
164
117
165
// 绘制矩阵单元格
118
166
matrix . forEach ( ( row , i ) => {
@@ -148,9 +196,16 @@ const CanvasComponent: React.FC<CanvasComponentProps> = ({ state, width, height
148
196
return (
149
197
< svg
150
198
ref = { svgRef }
151
- width = { width }
152
- height = { height }
153
- style = { { border : '1px solid #ccc' , borderRadius : '4px' } }
199
+ width = { dimensions . width }
200
+ height = { dimensions . height }
201
+ style = { {
202
+ border : '1px solid #ccc' ,
203
+ borderRadius : '4px' ,
204
+ width : '100%' ,
205
+ height : '100%'
206
+ } }
207
+ preserveAspectRatio = "xMidYMid meet"
208
+ viewBox = { `0 0 ${ dimensions . width } ${ dimensions . height } ` }
154
209
/>
155
210
) ;
156
211
} ;
0 commit comments