arbor.jsとは、下記のサンプルサイトのようなグラフが書けるライブラリです。
使ってみようと調べたところ、日本語の紹介サイトが少なかった1ので、まとめてみました。
こんなのが簡単に作れるようになります。
環境
- Mac OSX 10.11.6
- Safari 9.1.2
- google Chrome 57.0.2987.133 (64-bit)
初期構成
まずは、小さいグラフでnodeを■で表示するだけの物を作ってみます。
ファイル配置
ライブラリは、jQueryとarbor.jsに加え、graphics.jsを使います。このファイルもarbor.js内においてあります。
まずは、libフォルダから、arbor.jsとarbor-tween.jsをコピーします。
次に、demos/_フォルダから、jQueryのファイルと、graphics.jsをコピーします。
最後に、index.htmlファイルとsample.jsファイルを作成します。ファイルの中身は、ソースコードの項に記載します。
ソースコード
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="js/jquery-1.6.1.min.js"></script>
<script src="js/jquery.address-1.4.min.js"></script>
<script src="js/arbor.js"></script>
<script src="js/arbor-tween.js"></script>
<script src="js/graphics.js"></script>
<script src="js/sample.js"></script>
<title>arbor.jsサンプル</title>
</head>
<body>
<h1>arbor.js</h1>
<canvas id="viewport" width="280" height="280"></canvas>
</body>
</html>
(function($){
var Renderer = function(canvas){
var canvas = $(canvas).get(0)
var ctx = canvas.getContext("2d");
var particleSystem
var that = {
init:function(system){
particleSystem = system
particleSystem.screenSize(canvas.width, canvas.height)
particleSystem.screenPadding(80)
that.initMouseHandling()
},
redraw:function(){
ctx.fillStyle = "white"
ctx.fillRect(0,0, canvas.width, canvas.height)
particleSystem.eachEdge(function(edge, pt1, pt2){
ctx.strokeStyle = "rgba(0,0,0, .333)"
ctx.lineWidth = 1
ctx.beginPath()
ctx.moveTo(pt1.x, pt1.y)
ctx.lineTo(pt2.x, pt2.y)
ctx.stroke()
})
particleSystem.eachNode(function(node, pt){
var w = 10
ctx.fillStyle = "rgba(0,0,0, 1)"
ctx.fillRect(pt.x-w/2, pt.y-w/2, w,w)
})
},
initMouseHandling:function(){
var dragged = null;
var handler = {
clicked:function(e){
var pos = $(canvas).offset();
_mouseP = arbor.Point(e.pageX-pos.left, e.pageY-pos.top)
dragged = particleSystem.nearest(_mouseP);
if (dragged && dragged.node !== null){
dragged.node.fixed = true
}
$(canvas).bind('mousemove', handler.dragged)
$(window).bind('mouseup', handler.dropped)
return false
},
dragged:function(e){
var pos = $(canvas).offset();
var s = arbor.Point(e.pageX-pos.left, e.pageY-pos.top)
if (dragged && dragged.node !== null){
var p = particleSystem.fromScreen(s)
dragged.node.p = p
}
return false
},
dropped:function(e){
if (dragged===null || dragged.node===undefined) return
if (dragged.node !== null) dragged.node.fixed = false
dragged.node.tempMass = 1000
dragged = null
$(canvas).unbind('mousemove', handler.dragged)
$(window).unbind('mouseup', handler.dropped)
_mouseP = null
return false
}
}
$(canvas).mousedown(handler.clicked);
},
}
return that
}
$(document).ready(function(){
var sys = arbor.ParticleSystem(1000, 60, 0.5)
sys.parameters({gravity:true})
sys.renderer = Renderer("#viewport")
sys.addNode('z')
sys.addEdge('a','b')
})
})(this.jQuery)
これでひとまず、動くものが出来ました。
htmファイル解説
jsのライブラリを読み込んでいます。canvas上に描画するため、canvasも置いています。idは、sample.jsで利用します。
htmlファイルは、最後までこれを使います。
jsファイル解説
particleSystem.eachEdge(function(edge, pt1, pt2){
この部分は、edge(nodeとnodeを結ぶ線)の描画処理です。この中でEdgeの見た目を決めています。今回は、半透明の黒で細い線を描画しています。
particleSystem.eachNode(function(node, pt)
この部分は、nodeの描画処理です。この中でnodeの見た目を決めています。今回は黒い小さな■を描画しています。
initMouseHandling:function
マウス操作の処理です。今回はnodeをつかんで移動させる処理が書いてあります。
$(document).ready(function()
グラフの定義が書いてあります。この部分のAPIはリファレンスに記載がありますので、
詳しくはそちらをご確認ください。
今回は、反発:1000、張力:600、摩擦:0.5で物理世界を構築しています。
aとbが線(Edge)で繋がっているnodeでzが単独のnodeです。
適当に数値やAPIを変えると動作がわかりやすいかもしれません。
見た目を変更する
node名を表示する
nodeの描画処理を変更します。nodeにnode名を入れるように変更しました。
宣言部分に追加します。
var gfx = arbor.Graphics(canvas);
変える部分は、上記のnodeの描画処理の部分です。particleSystem.eachNodeに、addNodeで設定したnodeが入ってくるので、node.nameでnode名を取り出して、描画しています。
var nodeBoxes = {}
particleSystem.eachNode(function(node, pt){
var label = node.name||""
var w = ctx.measureText(""+label).width + 10
if (!(""+label).match(/^[ \t]*$/)){
pt.x = Math.floor(pt.x)
pt.y = Math.floor(pt.y)
}else{
label = null
}
ctx.fillStyle = "rgba(0,0,0,1)"
gfx.rect(pt.x-w/2, pt.y-10, w,20, 4, {fill:ctx.fillStyle})
nodeBoxes[node.name] = [pt.x-w/2, pt.y-11, w, 22]
// draw the text
if (label){
ctx.font = "12px Helvetica"
ctx.textAlign = "center"
ctx.fillStyle = "white"
if (node.data.color=='none') ctx.fillStyle = '#333333'
ctx.fillText(label||"", pt.x, pt.y+4)
ctx.fillText(label||"", pt.x, pt.y+4)
}
})
nodeの色や形を変えてみる
描画は、色を付けたり半透明を使ったり円を使ったり自由度が高いです。例として徳川家康の家系図を描画してみました。
sys.addNode('徳川家康',{who:"ieyasu"});
sys.addNode('養珠院',{who:"sokushitsu"});
sys.addNode('宝台院',{who:"sokushitsu"});
sys.addEdge('徳川家康','養珠院');
sys.addEdge('徳川家康','頼房');
sys.addEdge('徳川家康','頼宣');
sys.addEdge('徳川家康','宝台院');
sys.addEdge('徳川家康','秀忠');
sys.addEdge('養珠院','頼房');
sys.addEdge('養珠院','頼宣');
sys.addEdge('宝台院','秀忠');
まずは、家系図の定義を書きます。今回は、特殊なnodeには、whoプロパティをセットしました。これを利用して形や色を変える分岐を作ります。
if(node.data.who == "ieyasu")
{
ctx.fillStyle = "rgba(150,5,10,0.666)"
gfx.oval(pt.x-w/2, pt.y-w/2, w,w, {fill:ctx.fillStyle})
}
else if(node.data.who == "sokushitsu")
{
ctx.fillStyle = "rgba(0,20,150,0.666)"
gfx.rect(pt.x-w/2, pt.y-10, w,20, 4, {fill:ctx.fillStyle})
}
else
{
ctx.fillStyle = "rgba(0,0,0,0.666)"
gfx.rect(pt.x-w/2, pt.y-10, w,20, 4, {fill:ctx.fillStyle})
}
nodeBoxes[node.name] = [pt.x-w/2, pt.y-11, w, 22]
このコードをgithub上に置きました。
nodeを画像に置き換える
canvasの画像表示機能を使います。
particleSystem.eachNode(function(node, pt){
var label = node.name||""
var w = ctx.measureText(""+label).width + 20
if (!(""+label).match(/^[ \t]*$/)){
pt.x = Math.floor(pt.x)
pt.y = Math.floor(pt.y)
}else{
label = null
}
var img = new Image();
if(node.name == "AM")
{
img.src = "resource/AM.png";
ctx.drawImage(img, pt.x-w/2, pt.y-w/2, w,w);
}
else if(node.name == "CH")
{
img.src = "resource/CH.png";
ctx.drawImage(img, pt.x-w/2, pt.y-w/2, w,w);
}
else if(node.name == "CN")
{
img.src = "resource/CN.png";
ctx.drawImage(img, pt.x-w/2, pt.y-w/2, w,w);
}
else if(node.name == "EN")
{
img.src = "resource/EN.png";
ctx.drawImage(img, pt.x-w/2, pt.y-w/2, w,w);
}
else if(node.name == "JP")
{
img.src = "resource/JP.png";
ctx.drawImage(img, pt.x-w/2, pt.y-w/2, w,w);
}
else
{
ctx.fillStyle = "rgba(0,0,0,0.666)"
gfx.rect(pt.x-w/2, pt.y-10, w,20, 4, {fill:ctx.fillStyle})
}
nodeBoxes[node.name] = [pt.x-w/2, pt.y-11, w, 22]
})
nodeの設定をします。
var sys = arbor.ParticleSystem(1000, 60, 0.5)
sys.parameters({gravity:true})
sys.renderer = Renderer("#viewport")
sys.addNode('AM');
sys.addNode('CH');
sys.addNode('JP');
sys.addNode('CN');
sys.addNode('EN');
sys.addEdge('AM','CN');
sys.addEdge('CN','JP');
sys.addEdge('JP','CH');
sys.addEdge('CH','EN');
sys.addEdge('EN','AM');
srcは、githubのリポジトリのimageブランチをご確認ください。
使用例
使いどころが難しそうなライブラリですが、分散ネットワーク(マストドンなど)を俯瞰して眺める用や、ニューラルネットの表現などにも使える可能性があるように思います。
他にも数学的な表現で使える場面があるのではないかと思います。
試しに、ある数字に割り算可能な数字が何であるかをグラフにしてみました。
srcは、githubのリポジトリのgh-pagesブランチをご確認ください。
上記の動くサンプルサイトを置きました。
https://takahiroyamamoto.github.io/arbor-sample/
アルゴリズム
function factoring(seed,parent)
{
for(var i = Math.floor(seed/2) ; 1 < i ; i--)
{
var alreadyHave = false;
if(seed % i == 0)
{
fact.push({'parent':parent,'seed':seed,'value':i});
factoring(i,seed);
}
}
}
seedの数で割り切れるiを探索し、見つけたらそれを保存して再帰探索するものです。
実際に見つかったのは、このサイトのみなのですが、大変参考にさせて頂きました。ありがとうございました。