youngtsai commited on
Commit
fe5b5ac
·
1 Parent(s): 1881365
src/components/KnowledgeGraph/KnowledgeGraph.jsx CHANGED
@@ -11,6 +11,7 @@ import ReactFlow, {
11
  import dagre from 'dagre'; // 需要先安裝:npm install dagre
12
  import 'reactflow/dist/style.css';
13
  import axios from 'axios';
 
14
 
15
  const getLayoutedElements = (nodes, edges) => {
16
  const dagreGraph = new dagre.graphlib.Graph();
@@ -92,6 +93,14 @@ const graphDataCache = {
92
  current: null
93
  };
94
 
 
 
 
 
 
 
 
 
95
  function KnowledgeGraph({ nodes, edges, onNodeClick, selectedNodeId, graphId }) {
96
  const containerRef = useRef(null);
97
  const [flowNodes, setFlowNodes] = useNodesState([]);
@@ -99,7 +108,8 @@ function KnowledgeGraph({ nodes, edges, onNodeClick, selectedNodeId, graphId })
99
  const [localNodes, setLocalNodes] = useState(nodes);
100
  const [localEdges, setLocalEdges] = useState(edges);
101
  const [isLoading, setIsLoading] = useState(false);
102
-
 
103
  // 當 props 中的 nodes 和 edges 變化時,更新本地狀態
104
  useEffect(() => {
105
  if (nodes && nodes.length > 0) {
@@ -150,14 +160,79 @@ function KnowledgeGraph({ nodes, edges, onNodeClick, selectedNodeId, graphId })
150
  }
151
  }, [localNodes, graphId, isLoading]);
152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  // 使用 useEffect 來更新 ReactFlow 的節點和邊
154
  useEffect(() => {
155
  if (!localNodes || localNodes.length === 0) return;
156
-
157
  try {
158
  const validNodes = Array.isArray(localNodes) ? localNodes : Object.values(localNodes);
159
-
160
- const layoutedNodes = getLayoutedElements(
161
  validNodes.map(node => ({
162
  id: node.id,
163
  data: { label: node.title },
@@ -173,7 +248,8 @@ function KnowledgeGraph({ nodes, edges, onNodeClick, selectedNodeId, graphId })
173
  textAlign: 'center'
174
  }
175
  })),
176
- localEdges || []
 
177
  );
178
 
179
  const styledEdges = (localEdges || []).map(edge => ({
@@ -217,7 +293,7 @@ function KnowledgeGraph({ nodes, edges, onNodeClick, selectedNodeId, graphId })
217
  } catch (error) {
218
  console.error('處理圖譜數據時出錯:', error);
219
  }
220
- }, [localNodes, localEdges, selectedNodeId, setFlowNodes, setFlowEdges]);
221
 
222
  // 使用 useCallback 來記憶化事件處理函數
223
  const handleNodeClick = useCallback((_, node) => {
@@ -293,7 +369,28 @@ function KnowledgeGraph({ nodes, edges, onNodeClick, selectedNodeId, graphId })
293
  }
294
 
295
  return (
296
- <div ref={containerRef} style={{ width: '100%', height: '100%' }}>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  <ReactFlowProvider>
298
  <ReactFlow
299
  nodes={flowNodes}
 
11
  import dagre from 'dagre'; // 需要先安裝:npm install dagre
12
  import 'reactflow/dist/style.css';
13
  import axios from 'axios';
14
+ import { Select } from 'antd'; // 需安裝 antd 或用原生 select
15
 
16
  const getLayoutedElements = (nodes, edges) => {
17
  const dagreGraph = new dagre.graphlib.Graph();
 
93
  current: null
94
  };
95
 
96
+ // 支援的 layout 類型
97
+ const LAYOUT_OPTIONS = [
98
+ { value: 'TB', label: '上到下(預設)' },
99
+ { value: 'LR', label: '左到右' },
100
+ { value: 'mindmap', label: '心智圖式' },
101
+ { value: 'waterfall', label: '瀑布式' }
102
+ ];
103
+
104
  function KnowledgeGraph({ nodes, edges, onNodeClick, selectedNodeId, graphId }) {
105
  const containerRef = useRef(null);
106
  const [flowNodes, setFlowNodes] = useNodesState([]);
 
108
  const [localNodes, setLocalNodes] = useState(nodes);
109
  const [localEdges, setLocalEdges] = useState(edges);
110
  const [isLoading, setIsLoading] = useState(false);
111
+ const [layoutType, setLayoutType] = useState('TB'); // 新增 layout 狀態
112
+
113
  // 當 props 中的 nodes 和 edges 變化時,更新本地狀態
114
  useEffect(() => {
115
  if (nodes && nodes.length > 0) {
 
160
  }
161
  }, [localNodes, graphId, isLoading]);
162
 
163
+ // 取得不同 layout 的節點位置
164
+ const getLayoutedNodes = useCallback((nodes, edges, layoutType) => {
165
+ if (layoutType === 'mindmap') {
166
+ // 簡單心智圖:root 在左,其他節點橫向展開
167
+ const root = nodes[0];
168
+ return nodes.map((node, i) => ({
169
+ ...node,
170
+ position: {
171
+ x: i * 250,
172
+ y: (i % 2 === 0 ? 1 : -1) * (80 + 80 * Math.floor(i / 2))
173
+ }
174
+ }));
175
+ }
176
+ if (layoutType === 'waterfall') {
177
+ // 簡單瀑布式:每層一行,橫向排列
178
+ return nodes.map((node, i) => ({
179
+ ...node,
180
+ position: {
181
+ x: (i % 4) * 220,
182
+ y: Math.floor(i / 4) * 120
183
+ }
184
+ }));
185
+ }
186
+ // 其餘用 dagre
187
+ return getLayoutedElements(
188
+ nodes,
189
+ edges,
190
+ layoutType
191
+ );
192
+ }, []);
193
+
194
+ // 修改 getLayoutedElements 支援 rankdir
195
+ const getLayoutedElements = (nodes, edges, rankdir = 'TB') => {
196
+ const dagreGraph = new dagre.graphlib.Graph();
197
+ dagreGraph.setDefaultEdgeLabel(() => ({}));
198
+ dagreGraph.setGraph({
199
+ rankdir,
200
+ nodesep: 150,
201
+ ranksep: 200,
202
+ edgesep: 80,
203
+ marginx: 50,
204
+ marginy: 50,
205
+ acyclicer: 'greedy',
206
+ ranker: 'network-simplex'
207
+ });
208
+ nodes.forEach((node) => {
209
+ dagreGraph.setNode(node.id, { width: 200, height: 60 });
210
+ });
211
+ edges.forEach((edge) => {
212
+ dagreGraph.setEdge(edge.source, edge.target);
213
+ });
214
+ dagre.layout(dagreGraph);
215
+ const layoutedNodes = nodes.map(node => {
216
+ const nodeWithPosition = dagreGraph.node(node.id);
217
+ const nodeWidth = 200;
218
+ const nodeHeight = 60;
219
+ return {
220
+ ...node,
221
+ position: {
222
+ x: Number.isFinite(nodeWithPosition.x) ? nodeWithPosition.x - nodeWidth / 2 : 0,
223
+ y: Number.isFinite(nodeWithPosition.y) ? nodeWithPosition.y - nodeHeight / 2 : 0
224
+ },
225
+ };
226
+ });
227
+ return layoutedNodes;
228
+ };
229
+
230
  // 使用 useEffect 來更新 ReactFlow 的節點和邊
231
  useEffect(() => {
232
  if (!localNodes || localNodes.length === 0) return;
 
233
  try {
234
  const validNodes = Array.isArray(localNodes) ? localNodes : Object.values(localNodes);
235
+ const layoutedNodes = getLayoutedNodes(
 
236
  validNodes.map(node => ({
237
  id: node.id,
238
  data: { label: node.title },
 
248
  textAlign: 'center'
249
  }
250
  })),
251
+ localEdges || [],
252
+ layoutType
253
  );
254
 
255
  const styledEdges = (localEdges || []).map(edge => ({
 
293
  } catch (error) {
294
  console.error('處理圖譜數據時出錯:', error);
295
  }
296
+ }, [localNodes, localEdges, selectedNodeId, setFlowNodes, setFlowEdges, layoutType, getLayoutedNodes]);
297
 
298
  // 使用 useCallback 來記憶化事件處理函數
299
  const handleNodeClick = useCallback((_, node) => {
 
369
  }
370
 
371
  return (
372
+ <div ref={containerRef} style={{ width: '100%', height: '100%', position: 'relative' }}>
373
+ {/* 只保留 layout 選擇器 */}
374
+ <div style={{
375
+ position: 'absolute',
376
+ top: 12,
377
+ right: 12,
378
+ zIndex: 10,
379
+ background: '#fff',
380
+ borderRadius: 6,
381
+ boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
382
+ padding: '4px 12px'
383
+ }}>
384
+ <select
385
+ value={layoutType}
386
+ onChange={e => setLayoutType(e.target.value)}
387
+ style={{ fontSize: 14, padding: '2px 8px' }}
388
+ >
389
+ {LAYOUT_OPTIONS.map(opt => (
390
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
391
+ ))}
392
+ </select>
393
+ </div>
394
  <ReactFlowProvider>
395
  <ReactFlow
396
  nodes={flowNodes}