Cytoscape.js 是一個處理資料視覺化的 javascript library,當我們要對資料關係進行可視化顯示時,例如社交網路關係或網路拓樸圖時,Cytoscape.js 是個不錯的選擇。
Cytoscape 和 Cytoscape.js 是兩個完全獨立不同的軟體
sample1
建立一個 圓形排列 的 4 個節點 (A, B, C, D),節點之間有箭頭連線,點擊節點會有事件
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>test1</title>
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
<style>
#cy {
width: 800px;
height: 600px;
border: 1px solid #ccc;
display: block;
}
</style>
</head>
<body>
<h2>test1</h2>
<div id="cy"></div>
<script>
const cy = cytoscape({
container: document.getElementById('cy'),
elements: [
{ data: { id: 'a', label: '節點 A' } },
{ data: { id: 'b', label: '節點 B' } },
{ data: { id: 'c', label: '節點 C' } },
{ data: { id: 'd', label: '節點 D' } },
{ data: { id: 'ab', source: 'a', target: 'b' } },
{ data: { id: 'bc', source: 'b', target: 'c' } },
{ data: { id: 'cd', source: 'c', target: 'd' } },
{ data: { id: 'da', source: 'd', target: 'a' } }
],
style: [
{
selector: 'node',
style: {
'background-color': '#0074D9',
'label': 'data(label)',
'color': '#fff',
'text-valign': 'center',
'text-outline-width': 2,
'text-outline-color': '#0074D9'
}
},
{
selector: 'edge',
style: {
'width': 3,
'line-color': '#AAAAAA',
'target-arrow-color': '#AAAAAA',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier',
}
}
],
layout: {
name: 'circle'
}
});
// 點擊事件
cy.on('tap', 'node', function(evt) {
let node = evt.target;
console.log('你點了節點: ' + node.id());
});
</script>
</body>
</html>
sample2
流程圖,layout 調整為 dagre extension。
使用時要引用 dagre library 及 Cytoscape.js 的 extension
dagre 正是 Cytoscape.js 常用來畫flowchart或 directed graph 的 layout。適合做 flowchart, network topology, workflow
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>Cytoscape.js Flowchart</title>
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
<script src="https://unpkg.com/dagre/dist/dagre.min.js"></script>
<script src="https://unpkg.com/cytoscape-dagre/cytoscape-dagre.js"></script>
<style>
#cy {
width: 800px;
height: 600px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<h2>flowchart</h2>
<div id="cy"></div>
<script>
cytoscape.use(cytoscapeDagre);
const cy = cytoscape({
container: document.getElementById('cy'),
elements: [
{ data: { id: 'start', label: '開始' } },
{ data: { id: 'step1', label: '步驟 1' } },
{ data: { id: 'step2', label: '步驟 2' } },
{ data: { id: 'decision', label: '判斷 ?' } },
{ data: { id: 'end', label: '結束' } },
{ data: { id: 's1', source: 'start', target: 'step1' } },
{ data: { id: 's2', source: 'step1', target: 'step2' } },
{ data: { id: 's3', source: 'step2', target: 'decision' } },
{ data: { id: 's4', source: 'decision', target: 'end' } },
{ data: { id: 's5', source: 'decision', target: 'step1' } }
],
style: [
{
selector: 'node',
style: {
'shape': 'round-rectangle',
'background-color': '#28a745',
'label': 'data(label)',
'color': '#fff',
'text-valign': 'center',
'text-outline-width': 2,
'text-outline-color': '#28a745'
}
},
{
selector: 'node[id="decision"]',
style: {
'shape': 'diamond',
'background-color': '#ffc107',
'text-outline-color': '#ffc107'
}
},
{
selector: 'edge',
style: {
'width': 2,
'line-color': '#555',
'target-arrow-color': '#555',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier',
}
}
],
layout: {
name: 'dagre',
// rankDir: 'TB' // top-to-bottom
rankDir: 'LR' // 由左到右 排列
}
});
</script>
</body>
</html>
sample3
鐵路模擬,增加火車在鐵軌上移動的動畫
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>Cytoscape.js Railway</title>
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
<style>
#cy {
width: 800px;
height: 600px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<h2>Railway</h2>
<div id="cy"></div>
<script>
const cy = cytoscape({
container: document.getElementById('cy'),
elements: [
{ data: { id: 'station1', label: '車站 1' } },
{ data: { id: 'station2', label: '車站 2' } },
{ data: { id: 'station3', label: '車站 3' } },
{ data: { id: 'checkpoint4', label: '檢查點 4' } },
{ data: { id: 'checkpoint5', label: '檢查點 5' } },
{ data: { id: 'checkpoint6', label: '檢查點 6' } },
{ data: { id: 's1', source: 'station1', target: 'station2' } },
{ data: { id: 's2', source: 'station2', target: 'station3' } },
{ data: { id: 's3', source: 'station2', target: 'checkpoint4' } },
{ data: { id: 's4', source: 'checkpoint4', target: 'checkpoint5' } },
{ data: { id: 's5', source: 'checkpoint5', target: 'station3' } },
{ data: { id: 's6', source: 'checkpoint5', target: 'checkpoint6' } },
// 列車節點
{ data: { id: 'train1', label: '🚆' }, classes: 'train' }
],
style: [
{
selector: 'node',
style: {
'shape': 'ellipse',
'background-color': '#0074D9',
'label': 'data(label)',
'color': '#fff',
'text-valign': 'center',
'text-outline-width': 2,
'text-outline-color': '#0074D9'
}
},
{
selector: 'node[id^="station"]',
style: {
'shape': 'round-rectangle',
'background-color': '#17a2b8',
'text-outline-color': '#17a2b8'
}
},
{
selector: 'node.train',
style: {
'background-color': 'red',
'shape': 'ellipse',
'label': 'data(label)',
'font-size': 24,
'width': 30,
'height': 30
}
},
{
selector: 'edge',
style: {
'width': 2,
'line-color': '#555',
'target-arrow-color': '#555',
'target-arrow-shape': 'triangle'
}
}
],
layout: {
name: 'breadthfirst',
directed: true,
padding: 20
}
});
function moveAlongEdge(train, fromNode, toNode, duration, callback) {
const start = fromNode.position();
const end = toNode.position();
const startTime = performance.now();
function animate(now) {
const elapsed = now - startTime;
const t = Math.min(elapsed / duration, 1); // 0~1
const x = start.x + (end.x - start.x) * t;
const y = start.y + (end.y - start.y) * t;
train.position({ x, y });
if (t < 1) {
requestAnimationFrame(animate);
} else if (callback) {
callback();
}
}
requestAnimationFrame(animate);
}
function moveTrain(path) {
let i = 0;
const train = cy.getElementById('train1');
function step() {
if (i >= path.length - 1) return;
const fromNode = cy.getElementById(path[i]);
const toNode = cy.getElementById(path[i + 1]);
moveAlongEdge(train, fromNode, toNode, 2000, () => {
i++;
step();
});
}
step();
}
// 定義路徑
const route = ['station1', 'station2', 'checkpoint4', 'checkpoint5', 'station3'];
// 初始化列車位置
cy.getElementById('train1').position(cy.getElementById(route[0]).position());
// 2 秒後啟動列車
setTimeout(() => moveTrain(route), 2000);
</script>
</body>
</html>