Skip to content

Commit dfe3039

Browse files
authored
Merge pull request #92 from DMU-DebugVisual/inseong2
Inseong2
2 parents d0fa9e1 + b4062ae commit dfe3039

File tree

10 files changed

+1360
-954
lines changed

10 files changed

+1360
-954
lines changed

src/components/ide/AnimationFactory.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import React from 'react';
44
// 실제 애니메이션 컴포넌트들 import
55
import BubbleSortAnimation from './animations/SortAnimation';
66
import LinkedListAnimation from "./animations/LinkedListAnimation";
7-
import {Link} from "react-router-dom";
87
import BinaryTreeAnimation from "./animations/BinaryTreeAnimation";
98
import HeapAnimation from "./animations/HeapAnimation";
109
import GraphAnimation from "./animations/GraphAnimation";

src/components/ide/IDE.jsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// IDE.jsx - ModernSidebar 디자인 통합 버전 (사이드바 접힘 시 에디터 레이아웃 자동 조정)
22

33
import React, { useState, useEffect, useRef } from 'react';
4-
import { Link, useNavigate, useLocation, useParams } from 'react-router-dom';
54
import Editor from '@monaco-editor/react';
65
import VisualizationModal from './VisualizationModal';
76
import './IDE.css';
@@ -346,10 +345,6 @@ int main() {
346345
// 기본 상태들
347346
const [isLoggedIn, setIsLoggedIn] = useState(false);
348347
const [username, setUsername] = useState('');
349-
const navigate = useNavigate();
350-
const location = useLocation();
351-
const params = useParams();
352-
353348
const [code, setCode] = useState('# 여기에 코드를 입력하세요');
354349
const [fileName, setFileName] = useState("untitled.py");
355350
const [isSaved, setIsSaved] = useState(true);

src/components/ide/VisualizationModal.jsx

Lines changed: 263 additions & 193 deletions
Large diffs are not rendered by default.

src/components/ide/animations/GraphAnimation.jsx

Lines changed: 187 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,150 +1,238 @@
1-
import React, { useEffect, useRef } from "react";
2-
import * as d3 from "d3";
1+
import React, { useEffect, useMemo, useRef } from 'react';
2+
import * as d3 from 'd3';
33

44
const GraphAnimation = ({ data, currentStep }) => {
55
const svgRef = useRef(null);
6+
const frames = data?.frames || [];
7+
const frame = frames[currentStep] || null;
8+
const previousFrame = frames[currentStep - 1] || null;
9+
const vertexCount = useMemo(
10+
() => frame?.vertexCount ?? previousFrame?.vertexCount ?? 0,
11+
[frame, previousFrame]
12+
);
613

7-
const getVariableStateAt = (index, steps, variables) => {
8-
const vars = {};
9-
variables?.forEach(v => vars[v.name] = v.initialValue);
10-
11-
for (let i = 0; i <= index; i++) {
12-
const changes = steps[i]?.changes;
13-
if (changes) {
14-
for (const change of changes) {
15-
vars[change.variable] = change.after;
16-
}
17-
}
18-
}
19-
return vars;
20-
};
14+
const edges = useMemo(
15+
() => frame?.edges ?? previousFrame?.edges ?? [],
16+
[frame, previousFrame]
17+
);
18+
19+
const adjacency = useMemo(
20+
() => frame?.adjacency ?? previousFrame?.adjacency ?? [],
21+
[frame, previousFrame]
22+
);
23+
24+
const highlightedEdge = useMemo(
25+
() => frame?.highlight?.edge ?? previousFrame?.highlight?.edge ?? null,
26+
[frame, previousFrame]
27+
);
2128

22-
const drawGraph = (nodes, edges) => {
29+
useEffect(() => {
2330
const svg = d3.select(svgRef.current);
24-
svg.selectAll("*").remove();
31+
svg.selectAll('*').remove();
32+
33+
if (!frame && !previousFrame) {
34+
return;
35+
}
2536

2637
const width = 600;
2738
const height = 280;
2839

29-
const nodeObjs = nodes.map(id => ({ id }));
30-
const nodeMap = Object.fromEntries(nodeObjs.map(n => [n.id, n]));
31-
32-
const linkObjs = edges.map(([source, target]) => ({
33-
source: nodeMap[source],
34-
target: nodeMap[target]
40+
const nodes = Array.from({ length: vertexCount }, (_, id) => ({ id }));
41+
const links = edges.map(([source, target]) => ({
42+
source,
43+
target,
44+
highlighted: highlightedEdge && (
45+
(highlightedEdge[0] === source && highlightedEdge[1] === target) ||
46+
(highlightedEdge[0] === target && highlightedEdge[1] === source)
47+
)
3548
}));
3649

37-
const simulation = d3.forceSimulation(nodeObjs)
38-
.force("link", d3.forceLink(linkObjs).id(d => d.id).distance(100))
39-
.force("charge", d3.forceManyBody().strength(-300))
40-
.force("center", d3.forceCenter(width / 2, height / 2));
50+
const simulation = d3.forceSimulation(nodes)
51+
.force('charge', d3.forceManyBody().strength(-400))
52+
.force('center', d3.forceCenter(width / 2, height / 2))
53+
.force('link', d3.forceLink(links).id(d => d.id).distance(120))
54+
.force('collision', d3.forceCollide().radius(40));
4155

42-
const g = svg.append("g");
56+
const g = svg.append('g');
4357

44-
const link = g.selectAll("line")
45-
.data(linkObjs)
58+
const link = g.selectAll('line')
59+
.data(links)
4660
.enter()
47-
.append("line")
48-
.attr("stroke", "#aaa")
49-
.attr("stroke-width", 2);
61+
.append('line')
62+
.attr('stroke', d => d.highlighted ? '#f97316' : '#94a3b8')
63+
.attr('stroke-width', d => d.highlighted ? 4 : 2);
5064

51-
const node = g.selectAll("circle")
52-
.data(nodeObjs)
65+
const node = g.selectAll('circle')
66+
.data(nodes)
5367
.enter()
54-
.append("circle")
55-
.attr("r", 20)
56-
.attr("fill", "#90caf9");
57-
58-
const label = g.selectAll("text")
59-
.data(nodeObjs)
68+
.append('circle')
69+
.attr('r', 22)
70+
.attr('fill', '#60a5fa')
71+
.attr('stroke', '#0f172a')
72+
.attr('stroke-width', 1.5);
73+
74+
const label = g.selectAll('text')
75+
.data(nodes)
6076
.enter()
61-
.append("text")
62-
.text(d => d.id)
63-
.attr("text-anchor", "middle")
64-
.attr("dy", 5);
65-
66-
simulation.on("tick", () => {
77+
.append('text')
78+
.attr('text-anchor', 'middle')
79+
.attr('font-size', 14)
80+
.attr('font-weight', '600')
81+
.attr('fill', '#0f172a')
82+
.text(d => d.id);
83+
84+
simulation.on('tick', () => {
6785
link
68-
.attr("x1", d => d.source.x ?? 0)
69-
.attr("y1", d => d.source.y ?? 0)
70-
.attr("x2", d => d.target.x ?? 0)
71-
.attr("y2", d => d.target.y ?? 0);
86+
.attr('x1', d => d.source.x ?? 0)
87+
.attr('y1', d => d.source.y ?? 0)
88+
.attr('x2', d => d.target.x ?? 0)
89+
.attr('y2', d => d.target.y ?? 0);
7290

7391
node
74-
.attr("cx", d => d.x ?? 0)
75-
.attr("cy", d => d.y ?? 0);
92+
.attr('cx', d => d.x ?? 0)
93+
.attr('cy', d => d.y ?? 0);
7694

7795
label
78-
.attr("x", d => d.x ?? 0)
79-
.attr("y", d => d.y ?? 0);
96+
.attr('x', d => d.x ?? 0)
97+
.attr('y', d => (d.y ?? 0) + 5);
8098
});
81-
};
82-
83-
const drawStep = (stepIndex) => {
84-
const step = data.steps?.[stepIndex];
85-
const structure = step?.dataStructure;
86-
if (structure?.type === "graph") {
87-
const nodes = structure.nodes;
88-
const edges = structure.edges || [];
89-
drawGraph(nodes, edges);
90-
}
91-
};
9299

93-
useEffect(() => {
94-
if (!data?.steps || !Array.isArray(data.steps)) return;
95-
if (currentStep < 0 || currentStep >= data.steps.length) return;
96-
drawStep(currentStep);
97-
}, [currentStep, data]);
100+
return () => simulation.stop();
101+
}, [frame, previousFrame, vertexCount, edges, highlightedEdge]);
98102

99-
const steps = data?.steps || [];
100-
const variables = data?.variables || [];
101-
const currentVariables = getVariableStateAt(currentStep, steps, variables);
102-
const stepData = steps[currentStep];
103+
const description = frame?.description || '그래프 상태를 추적합니다.';
103104

104105
return (
105106
<div style={{
106107
width: '100%',
107108
height: '100%',
108-
maxHeight: '600px',
109+
maxHeight: '640px',
109110
padding: '16px',
110111
display: 'flex',
111112
flexDirection: 'column',
112113
gap: '16px',
113114
fontFamily: '"Segoe UI", sans-serif',
114115
overflow: 'auto'
115116
}}>
116-
{/* 현재 단계 설명 */}
117-
{stepData?.description && (
118-
<div style={{
119-
padding: '12px',
120-
background: '#f0f9ff',
121-
borderRadius: '8px',
122-
borderLeft: '4px solid #6a5acd',
123-
fontSize: '14px',
124-
color: '#1e293b',
125-
fontStyle: 'italic'
126-
}}>
127-
<strong>Step {currentStep + 1}:</strong> {stepData.description}
128-
</div>
129-
)}
117+
<div style={{
118+
padding: '12px',
119+
background: '#f8fafc',
120+
borderRadius: '8px',
121+
borderLeft: '4px solid #2563eb',
122+
fontSize: '14px',
123+
color: '#1e293b'
124+
}}>
125+
<strong>Step {currentStep + 1} / {frames.length}:</strong> {description}
126+
</div>
130127

131-
{/* 🎯 D3 SVG 시각화 영역 */}
132128
<div style={{
133129
flex: 1,
134130
display: 'flex',
135131
alignItems: 'center',
136132
justifyContent: 'center',
137133
background: '#ffffff',
138-
border: '1px solid #eee',
134+
border: '1px solid #e2e8f0',
139135
borderRadius: '8px',
140-
minHeight: '200px',
141-
maxHeight: '400px',
136+
minHeight: '260px',
142137
padding: '16px'
143138
}}>
144-
<svg ref={svgRef}
145-
width="600"
146-
height="280"
147-
style={{background: '#f9f9f9', borderRadius: '8px', border: '1px solid #ddd'}}></svg>
139+
<svg
140+
ref={svgRef}
141+
width="600"
142+
height="280"
143+
style={{ background: '#f1f5f9', borderRadius: '8px' }}
144+
/>
145+
</div>
146+
147+
<div style={{
148+
display: 'grid',
149+
gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))',
150+
gap: '10px'
151+
}}>
152+
<div style={{
153+
padding: '10px',
154+
background: '#f8fafc',
155+
borderRadius: '8px',
156+
border: '1px solid #e2e8f0',
157+
textAlign: 'center',
158+
fontSize: '12px'
159+
}}>
160+
<div style={{ color: '#64748b', marginBottom: '4px' }}>정점 수</div>
161+
<div style={{ fontWeight: '700', color: '#0f172a' }}>{vertexCount}</div>
162+
</div>
163+
164+
<div style={{
165+
padding: '10px',
166+
background: '#f8fafc',
167+
borderRadius: '8px',
168+
border: '1px solid #e2e8f0',
169+
textAlign: 'center',
170+
fontSize: '12px'
171+
}}>
172+
<div style={{ color: '#64748b', marginBottom: '4px' }}>간선 수</div>
173+
<div style={{ fontWeight: '700', color: '#0f172a' }}>{edges.length}</div>
174+
</div>
175+
176+
{highlightedEdge && (
177+
<div style={{
178+
padding: '10px',
179+
background: '#fff7ed',
180+
borderRadius: '8px',
181+
border: '1px solid #fdba74',
182+
textAlign: 'center',
183+
fontSize: '12px',
184+
color: '#c2410c'
185+
}}>
186+
<div style={{ marginBottom: '4px' }}>강조 간선</div>
187+
<div style={{ fontWeight: '700' }}>({highlightedEdge[0]}, {highlightedEdge[1]})</div>
188+
</div>
189+
)}
190+
</div>
191+
192+
<div style={{
193+
background: '#ffffff',
194+
borderRadius: '8px',
195+
border: '1px solid #e2e8f0',
196+
padding: '12px'
197+
}}>
198+
<div style={{
199+
fontSize: '12px',
200+
color: '#475569',
201+
fontWeight: '600',
202+
marginBottom: '8px'
203+
}}>
204+
인접 행렬 (현재 상태)
205+
</div>
206+
<div style={{
207+
display: 'grid',
208+
gridTemplateColumns: `repeat(${vertexCount || 1}, minmax(24px, 1fr))`,
209+
gap: '6px'
210+
}}>
211+
{(adjacency || []).slice(0, vertexCount).map((row, rowIndex) => (
212+
row.slice(0, vertexCount).map((cell, colIndex) => {
213+
const isHighlighted = highlightedEdge && (
214+
(highlightedEdge[0] === rowIndex && highlightedEdge[1] === colIndex) ||
215+
(highlightedEdge[1] === rowIndex && highlightedEdge[0] === colIndex)
216+
);
217+
return (
218+
<div
219+
key={`${rowIndex}-${colIndex}`}
220+
style={{
221+
padding: '6px 4px',
222+
textAlign: 'center',
223+
background: isHighlighted ? '#fde68a' : '#f1f5f9',
224+
borderRadius: '4px',
225+
fontSize: '12px',
226+
fontWeight: '600',
227+
color: '#0f172a'
228+
}}
229+
>
230+
{cell}
231+
</div>
232+
);
233+
})
234+
))}
235+
</div>
148236
</div>
149237
</div>
150238
);

0 commit comments

Comments
 (0)