Vis.js Network Module

0.

최근 연구실에서 데이터 시각화 쪽을 담당했다. Vis.js는 Network, Timeline, Graph2d, Graph3d 등의 시각화 모듈을 지원하며, 이 시각화 모듈이 내부적으로 사용하는 자료구조가 DataSet이라는 모듈인 듯 하다. 이번 포스팅에서는 내가 담당했던 Network Module 부분만 간단하게 문서화하고자 한다. 다른 부분은 본인도 다뤄보지 않아서 잘 모르니 답변을 기대하지 마시길!

1.

vis.Network를 초기화하는 방법에 대해서 알아보자.

import vis from 'vis'; 로 vis를 불러와서  new vis.Network() 로 vis.Network 인스턴스를 초기화하면 된다. 이 때 필요한 생성자의 파라미터는 1. canvas를 끼워넣을 div element 2. 그래프를 그리는 데에 필요한 데이터 3. 그래프 렌더링 옵션이다.

1st param

첫 번째 파라미터는 간단하게 document.getElementById('mynetwork') 와 같은 형식으로 불러와서 넘겨주면 된다. 당연히, html 문서 상에 div#mynetwork 가 존재해야한다.

2nd param

데이터는 서두에서 언급한 vis.DataSet으로 넘겨줘도 되고, 일반적인 JS Object / Array로 넘겨줘도 된다.

위 코드에서 new vis.DataSet(, ) 를 지워도 잘 동작한다.

3rd param

어떻게 그래프를 그릴 것이냐에 대한 옵션이다. 디테일은 공식 문서를 확인하는 것이 빠르다. 여기서는 빈 객체를 넘겨서 다음과 같이 초기화하면 된다. const network = new vis.Network(container, data, {});

Result

Screenshot from 2017-08-01 13-51-46.png

결과는 이 링크에서 확인할 수 있는데, 새로고침을 여러 번 하면 그래프가 다양한 모양으로 그려지는 것을 눈치챈 분들 중에 이것이 거슬린다면, 3번째 파라미터로 option.layout.randomSeed를 undefined가 아닌 특정한 정수 값으로 넣자.

2.

Screenshot from 2017-08-01 14-02-14.png

이제 vis.Network를 클러스터링하는 방법에 대해 알아보자.

공식 문서를 보면 clusterByConnection(), clusterByHubSize() 등등 많은 클러스트 함수들이 있는 것을 확인할 수 있는데, 이 포스팅에서는 가장 기본이 되는 cluster() 함수만을 다룬다.

일단, 지금부터 얘기하는 cluster()를 포함한 메소드들은 특별한 언급이 없으면 위에서 new vis.Network() 를 통해 생성된 network 객체의 메소드를 사용하는 것으로 생각해도 무방하다.

  • cluster()는 옵션 객체 (clusteringOption라고 부르자) 를 받아서 네트워크를 클러스터링을 수행 하는데, 클러스터링된 네트워크를 다시 클러스터링하는 것도 가능하다.
  • 또한, clusteringOption.joinCondition() 함수 이외의 다른 옵션값들은 optional하다.
  • 클러스트 노드를 펼쳐서 자식 노드들을 보이게 하고 싶으면 openCluster()를 사용하자.

RDBMS의 데이터베이스, 테이블, 컬럼을 클러스터링한다고 가정하자. 모든 테이블들은 어떤 데이터베이스에 자식 노드이고, 컬럼은 그런 테이블들의 자식 노드다. 따라서 테이블1에 속하는 컬럼들 끼리 묶어서 컬럼들을 클러스터링한 뒤에, 다시 그 테이블들을 가지고 데이터베이스로 한 번 더 묶는 형식으로 진행하면 될 것이다.

여기서 컬럼들을 테이블1로 묶기 위한 clusteringOption을 만들어서, 그 옵션 객체를 넘겨 cluster()을 수행하고, 다시 컬럼들을 테이블2로 묶기위한 옵션을 새로 만들어서 다시 cluster()를 호출하는 과정을 for-loop등을 통해 반복해야한다. 위에서 언급한, 클러스터링 된 네트워크를 다시 클러스터링 할 수 있다는 뜻이 바로 이 의미이다.

이제 clusteringOption의 옵션값들로 무엇을 넣어줄 수 있는지 살펴보자.

joinCondition()

network 인스턴스 안에 있는 노드들을 순회하면서 어떤 노드들끼리 클러스터링 되어야 하는가를 함수로 넘겨주면 된다.

childOption이 인스턴스 내부의 네트워크 노드들이다. 그 인스턴스의 멤버변수와 묶고자 하는 테이블 이름을 묶어주면 되는데, 보통의 경우에 묶고자 하는 노드(테이블)이 한 개가 아닌 여러개이므로, 1 대신에 for 문에서 돌리고 있는 값을 넣어주면 되겠다.

processProperties()

joinCondition()를 네트워크의 모든 노드들에 대해 호출한 뒤에 클러스터 노드의 속성값을 ‘계산‘하는 데에 쓰인다. 클러스터 노드에 속하는 자식 노드들로 부터 계산되어야하는 노드의 질량같은 것이 그것인데, 더 역동적인 물리효과를 줄 수 있게 되지만 언급했듯, 옵션이다.

clusterNodeProperties

함수가 아니다. joinCondition()의 호출로 클러스터된 노드가 가져야할 정적인 속성들을 여기서 지정해주면 된다. 또한, clusterNodeProperties.allowSingleNodeCluster 가 기본적으로 false로 지정되어 있는데, 한 개의 자식노드만 가지는 경우에도 클러스터링 될 수 있게 설정하고 싶다면 true로 초기화하자.

3.

Vis.js 를 사용하면서 경험한 시행착오와 팁들을 공유해본다.

1. cluster() 적게 부르기

table1이 column1, 2, 3, 4를 자식 노드로 가지고,
table2가 column5, 6, 7, 8을 자식노드로 가지고,
column들의 배열인 columnList에는 column1~8까지가 순서대로 들어있다고 할 때,

columnList를 순회하면서 cluster()를 호출할 때, column1에서 table1에 대한 클러스터링을 이미 마친 상태라면, column2, 3, 4에 대해서는 cluster()를 호출하지 않아도 된다. cluster()를 호출할 때 내부적으로 joinCondition이 네트워크 노드의 개수만큼 불린다는 것을 다시 생각해보자.

이미 클러스터링이 완료된 경우를 제외하면 cluster를 적게 부를 수 있을 것이다.

2. 초기 네트워크 데이터 적게 넣기

vis.Network 객체를 초기화할 때, 파라미터로 넘겨지는 데이터의 노드 숫자를 줄일 수 있는 경우가 존재한다.

table1이 column1~4를 가지며,
table2가 column5~8을 가질 때,

vis.Network객체를 생성할 때 column만 넣어주고, 이후에 clustering을 하면서 clusterNodeProperty에 table 객체를 그대로 넣어주면 된다. 이렇게 클러스터링된 노드는 네트워크 객체 내부의 노드 DataSet에 추가되고, 이후에 cluster()를 호출할 때 순회대상이 된다.

요약하자면,
초기에 네트워크를 초기화하며 곧바로 클러스터링을 하면서,
클러스터 노드로 변환될 노드들은 굳이 처음부터 넣지 않아도 된다는 것이다.

위에 서술된 1번과 2번 팁을 굳이 따르지 않더라도 네트워크는 잘 그려진다. 퍼포먼스 팁이다.

3. clusterNodeProperties에 Object.assign() 사용하기

넣고자 하는 클러스터가 이미 있는 경우 + allowSingNodeCluster: true로 넣고 싶은 경우에 한해,


와 같이 새로운 객체를 만들 필요도 없고, 추가적인 구문이 필요하지 않은 이 코드를 사용하자.


  • DataSet이 내부적으로 해시를 사용하나 찾아봤는데, 별로 그런 냄새는 나지 않았다.
  • 아예 사용을 안한다고는 할 수 없는데, 그렇게 해놓고 딱히 적극적으로 사용하는 느낌도 아니였다고 하면 정확할 듯.
  • 찾아보며 코드를 읽었는데, ===, !== 를 사용하지 않았고, Class를 사용하기 보다는 Prototype을 사용하여 짠 옛날 느낌의 JS 였다. 아마 DataSet은 부족한 Plain JavaScript Array / Object의 유틸성에 못이겨 스스로 짠 자료구조가 아닐까 싶다.

Leave a Reply