Skip to content

Commit 142aa5a

Browse files
committed
feat: optimize layout for full screen usage
1 parent 85ea6ec commit 142aa5a

File tree

4 files changed

+122
-55
lines changed

4 files changed

+122
-55
lines changed

src/App.css

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#root {
2-
max-width: 1280px;
2+
max-width: 100%;
3+
width: 100%;
34
margin: 0 auto;
4-
padding: 2rem;
5+
padding: 0.5rem;
56
text-align: center;
67
}
78

@@ -68,15 +69,17 @@ body, html {
6869
flex-direction: column;
6970
height: 100vh;
7071
max-height: 100vh;
71-
padding: 10px;
72+
padding: 5px;
73+
overflow: hidden;
7274
}
7375

7476
.app-header {
7577
display: flex;
7678
justify-content: space-between;
7779
align-items: center;
78-
padding: 0 10px 10px 10px;
80+
padding: 0 10px 5px 10px;
7981
border-bottom: 1px solid var(--border-color);
82+
height: 50px;
8083
}
8184

8285
.app-header h1 {
@@ -100,13 +103,13 @@ body, html {
100103
.app-content {
101104
display: flex;
102105
flex: 1;
103-
height: calc(100vh - 80px);
106+
height: calc(100vh - 60px);
104107
overflow: hidden;
105108
}
106109

107110
.left-panel {
108-
width: 30%;
109-
padding: 10px;
111+
width: 25%;
112+
padding: 5px;
110113
overflow-y: auto;
111114
border-right: 1px solid var(--border-color);
112115
}
@@ -115,10 +118,15 @@ body, html {
115118
flex: 1;
116119
display: flex;
117120
flex-direction: column;
118-
padding: 10px;
121+
padding: 5px;
119122
overflow: hidden;
120123
}
121124

125+
.center-panel svg {
126+
flex: 1;
127+
min-height: 0;
128+
}
129+
122130
.algorithm-description {
123131
margin-top: 10px;
124132
padding: 10px;

src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ function App() {
108108
<div className="center-panel">
109109
<CanvasComponent
110110
state={state}
111-
width={600}
112-
height={400}
111+
width={window.innerWidth * 0.65}
112+
height={window.innerHeight * 0.7}
113113
/>
114114
<ControlPanel state={state} dispatch={dispatch} />
115115
</div>

src/components/CanvasComponent.tsx

Lines changed: 80 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useRef } from 'react';
1+
import React, { useEffect, useRef, useState } from 'react';
22
import * as d3 from 'd3';
33
import { AnimationState } from '../state/animationSlice';
44

@@ -22,6 +22,29 @@ interface CanvasComponentProps {
2222

2323
const CanvasComponent: React.FC<CanvasComponentProps> = ({ state, width, height }) => {
2424
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]);
2548

2649
// 渲染楼梯节点图
2750
useEffect(() => {
@@ -30,34 +53,54 @@ const CanvasComponent: React.FC<CanvasComponentProps> = ({ state, width, height
3053
const svg = d3.select(svgRef.current);
3154
svg.selectAll('*').remove(); // 清空画布
3255

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+
3365
// 创建图层
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;
3880

3981
// 绘制连接线
4082
linkGroup.selectAll('line')
4183
.data(state.staircase.links)
4284
.enter()
4385
.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))
4890
.attr('stroke', '#666')
4991
.attr('stroke-width', 2);
5092

5193
// 绘制节点
5294
const nodeColor = getColorByAlgorithm(state.currentAlgorithm);
95+
const nodeRadius = Math.min(20, Math.max(15, Math.min(innerWidth, innerHeight) / 30));
5396

5497
nodeGroup.selectAll('circle')
5598
.data(state.staircase.nodes)
5699
.enter()
57100
.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)
61104
.attr('fill', nodeColor)
62105
.attr('stroke', '#333')
63106
.attr('stroke-width', 2);
@@ -67,8 +110,8 @@ const CanvasComponent: React.FC<CanvasComponentProps> = ({ state, width, height
67110
.data(state.staircase.nodes)
68111
.enter()
69112
.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)
72115
.attr('text-anchor', 'middle')
73116
.attr('fill', '#fff')
74117
.attr('font-weight', 'bold')
@@ -77,20 +120,20 @@ const CanvasComponent: React.FC<CanvasComponentProps> = ({ state, width, height
77120
// 渲染公式
78121
if (state.formula) {
79122
formulaGroup.append('text')
80-
.attr('x', width - 50)
81-
.attr('y', 30)
123+
.attr('x', innerWidth - 20)
124+
.attr('y', 20)
82125
.attr('text-anchor', 'end')
83126
.attr('font-family', 'serif')
84-
.attr('font-size', '14px')
127+
.attr('font-size', `${Math.min(14, Math.max(10, innerWidth / 40))}px`)
85128
.text(state.formula);
86129
}
87130

88131
// 渲染矩阵(仅当使用矩阵算法时)
89132
if (state.currentAlgorithm === 'matrix' && state.matrix.length > 0) {
90-
renderMatrix(svg, state.matrix);
133+
renderMatrix(g, state.matrix, innerWidth, innerHeight);
91134
}
92135

93-
}, [state.staircase, state.currentAlgorithm, state.formula, state.matrix, width, height]);
136+
}, [state.staircase, state.currentAlgorithm, state.formula, state.matrix, dimensions]);
94137

95138
// 根据算法类型获取颜色
96139
const getColorByAlgorithm = (algorithm: AnimationState['currentAlgorithm']): string => {
@@ -107,12 +150,17 @@ const CanvasComponent: React.FC<CanvasComponentProps> = ({ state, width, height
107150
};
108151

109152
// 渲染矩阵
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')
112160
.attr('class', 'matrix')
113161
.attr('transform', `translate(${width - 100}, 50)`);
114162

115-
const cellSize = 30;
163+
const cellSize = Math.min(30, Math.max(20, width / 20));
116164

117165
// 绘制矩阵单元格
118166
matrix.forEach((row, i) => {
@@ -148,9 +196,16 @@ const CanvasComponent: React.FC<CanvasComponentProps> = ({ state, width, height
148196
return (
149197
<svg
150198
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}`}
154209
/>
155210
);
156211
};

src/components/ControlPanel.tsx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -170,59 +170,63 @@ const styles = {
170170
padding: '10px',
171171
backgroundColor: '#f5f5f5',
172172
borderRadius: '4px',
173-
marginBottom: '10px',
174-
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
173+
marginTop: '10px',
174+
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
175+
maxHeight: '30vh',
176+
overflowY: 'auto' as const
175177
},
176178
section: {
177-
marginBottom: '15px'
179+
marginBottom: '10px'
178180
},
179181
heading: {
180-
fontSize: '16px',
181-
marginBottom: '8px'
182+
fontSize: '14px',
183+
marginBottom: '6px'
182184
},
183185
buttonGroup: {
184186
display: 'flex',
185-
gap: '8px',
187+
gap: '5px',
186188
flexWrap: 'wrap' as const
187189
},
188190
algorithmButton: {
189-
padding: '6px 12px',
191+
padding: '4px 8px',
190192
border: 'none',
191193
borderRadius: '4px',
192194
cursor: 'pointer',
193-
transition: 'background-color 0.3s'
195+
transition: 'background-color 0.3s',
196+
fontSize: '12px'
194197
},
195198
controlButton: {
196-
padding: '6px 12px',
199+
padding: '4px 8px',
197200
backgroundColor: '#555',
198201
color: 'white',
199202
border: 'none',
200203
borderRadius: '4px',
201-
cursor: 'pointer'
204+
cursor: 'pointer',
205+
fontSize: '12px'
202206
},
203207
sliderContainer: {
204-
marginTop: '15px',
208+
marginTop: '8px',
205209
display: 'flex',
206210
flexDirection: 'column' as const,
207-
gap: '5px'
211+
gap: '3px'
212+
},
213+
slider: {
214+
width: '100%'
208215
},
209216
speedControlContainer: {
210-
marginTop: '15px',
217+
marginTop: '5px',
211218
display: 'flex',
212219
flexDirection: 'column' as const,
213-
gap: '5px'
214-
},
215-
slider: {
216-
width: '100%'
220+
gap: '3px'
217221
},
218222
description: {
219-
marginTop: '15px',
220-
padding: '10px',
223+
padding: '8px',
221224
backgroundColor: '#fff',
222225
borderRadius: '4px',
223226
border: '1px solid #ddd',
224227
minHeight: '20px',
225-
fontSize: '14px'
228+
fontSize: '12px',
229+
marginTop: '5px'
226230
}
227231
};
228232

0 commit comments

Comments
 (0)