本記事は図形をグリグリ動かせるアプリケーションを作るために図形ライブラリ使ってみたCytoscape編です。
JavaScriptを使っているもののデスクトップアプリケーションを作りたかったのでElectronを使用してます。
図形をグリグリ動かせる!無料JS図形ライブラリ5選比較してみた
図形をグリグリ動かすためのSigma.js+ Electron入門
図形をグリグリ動かすためのJSPlumb+ Electron入門
本記事👉図形をグリグリ動かすためのCytoscape+ Electron入門
図形をグリグリ動かすためのmxGraph+ Electron入門
図形をグリグリ動かすためのD3.js+ Electron入門
今回お試しで作ったやつはこんな感じ
Cytoscapeの使い方
Node.jsのインストール
本記事では、electron上で目的のライブラリを動作するため、まずはelectronを動かすうえで必要となるnode.jsをインストールしましょう。
package.json
{
"name": "sample",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "electron ."
},
"license": "ISC",
"devDependencies": {
"electron": "^35.1.4"
},
"dependencies": {
"cytoscape": "^3.28.1"
}
}
main.js
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Cytoscape Example</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
#cy {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.controls {
position: absolute;
top: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.8);
padding: 10px;
border-radius: 5px;
z-index: 1000;
}
button {
margin: 5px;
padding: 5px 10px;
}
</style>
</head>
<body>
<div class="controls">
<button id="circleLayout">Circle Layout</button>
<button id="gridLayout">Grid Layout</button>
<button id="randomLayout">Random Layout</button>
</div>
<div id="cy"></div>
<script src="node_modules/cytoscape/dist/cytoscape.min.js"></script>
<script src="renderer.js"></script>
</body>
</html>
renderer.js
document.addEventListener('DOMContentLoaded', () => {
let nodeCount = 0;
const cy = cytoscape({
container: document.getElementById('cy'),
style: [
{
selector: 'node',
style: {
'background-color': '#666',
'label': 'data(id)',
'width': 40,
'height': 40
}
},
{
selector: 'edge',
style: {
'width': 3,
'line-color': '#ccc',
'target-arrow-color': '#ccc',
'target-arrow-shape': 'triangle'
}
}
],
elements: {
nodes: [
{ data: { id: 'a' } },
{ data: { id: 'b' } },
{ data: { id: 'c' } }
],
edges: [
{ data: { source: 'a', target: 'b' } },
{ data: { source: 'b', target: 'c' } },
{ data: { source: 'c', target: 'a' } }
]
},
layout: {
name: 'circle'
}
});
// ノードの追加
cy.on('tap', function(evt) {
if (evt.target === cy) {
const pos = evt.position;
const newNodeId = 'node' + (++nodeCount);
cy.add({
group: 'nodes',
data: { id: newNodeId },
position: { x: pos.x, y: pos.y }
});
}
});
// ノードの削除
cy.on('cxttap', 'node', function(evt) {
const node = evt.target;
cy.remove(node);
});
// レイアウトの切り替え
document.getElementById('circleLayout').addEventListener('click', () => {
cy.layout({ name: 'circle' }).run();
});
document.getElementById('gridLayout').addEventListener('click', () => {
cy.layout({ name: 'grid' }).run();
});
document.getElementById('randomLayout').addEventListener('click', () => {
cy.layout({ name: 'random' }).run();
});
// ズーム機能
cy.on('mousewheel', function(evt) {
const zoom = evt.originalEvent.wheelDeltaY > 0 ? 1.1 : 0.9;
cy.zoom({
level: cy.zoom() * zoom,
renderedPosition: { x: evt.renderedPosition.x, y: evt.renderedPosition.y }
});
});
// ノードのドラッグ&ドロップ
cy.nodes().on('drag', function(evt) {
const node = evt.target;
node.style('background-color', '#ff6b6b');
});
cy.nodes().on('dragfree', function(evt) {
const node = evt.target;
node.style('background-color', '#666');
});
});
ソースコード解説
基本的なセットアップ
const cy = cytoscape({
container: document.getElementById('cy'), // 描画するコンテナ要素
style: [ // ノードとエッジのスタイル定義
{
selector: 'node',
style: {
'background-color': '#666',
'label': 'data(id)',
'width': 40,
'height': 40
}
},
{
selector: 'edge',
style: {
'width': 3,
'line-color': '#ccc',
'target-arrow-color': '#ccc',
'target-arrow-shape': 'triangle'
}
}
]
});
ノードとエッジの追加
elements: {
nodes: [
{ data: { id: 'a' } },
{ data: { id: 'b' } },
{ data: { id: 'c' } }
],
edges: [
{ data: { source: 'a', target: 'b' } },
{ data: { source: 'b', target: 'c' } },
{ data: { source: 'c', target: 'a' } }
]
}
クリックイベントによるノードの追加
cy.on('tap', function(evt) {
if (evt.target === cy) {
const pos = evt.position;
const newNodeId = 'node' + (++nodeCount);
cy.add({
group: 'nodes',
data: { id: newNodeId },
position: { x: pos.x, y: pos.y }
});
}
});
クリックイベントによるノードの削除
cy.on('cxttap', 'node', function(evt) {
const node = evt.target;
cy.remove(node);
});
ドラッグ & ドロップ
cy.nodes().on('drag', function(evt) {
const node = evt.target;
node.style('background-color', '#ff6b6b');
});
cy.nodes().on('dragfree', function(evt) {
const node = evt.target;
node.style('background-color', '#666');
});
レイアウト変更
// サークルレイアウト
cy.layout({ name: 'circle' }).run();
// グリッドレイアウト
cy.layout({ name: 'grid' }).run();
// ランダムレイアウト
cy.layout({ name: 'random' }).run();
実行
ライブラリのインストール
npm install
アプリを起動!
npm start
終わりに
図形ライブラリって結構ソースコードの量が大きくなったり、複雑になってりすることが多いと思うんですがCytoscapeは比較的コード量が少なめで読みやすくていいですね!
デザインや機能的な面に関しても十分なライブラリといった感じで、だいぶ優秀なライブラリな印象です。
参考
https://github.com/fujarenpaw/codeForBlog/tree/main/code/electron/Cytoscape