본문 바로가기
FrontEnd

데이터 시각화 라이브러리 D3.js로 네트워크 차트 만들기 (1)

by MC매드 2023. 6. 23.

안녕하세요, 메가존클라우드 Mass Migration Center의 AAT에서 프론트엔드와 백엔드 개발을 담담하고 있는 MC매드(A.K.A. The Chart Generator)입니다.

 

마이그레이션 전에 진단 과정을 거치며 가장 중요한 것 중 하나가 바로 "호스트의 연계 복잡도"를 알아내는 일인데요. 현재 AAT가 개발 중인 MAD는 수집 엔진을 통해 호스트 간의 네트워크 구성 데이터를 수집하고, 해당 데이터를 웹에서 차트로 보여주고 있답니다.

 

차트는 자바스크립트 오픈소스 라이브러리인 D3.js를 활용하여 개발됐어요. 이번 포스팅에서는 D3.js 라이브러리를 활용해서 네트워크 차트를 개발하는 과정을 소개해보겠습니다!

 

 


01. 변수 선언하기

 

const chartOptsList = {
    selector: "#network-chart",
    width: 700,
    height: 700,
    url: //작성
};

createDataChart(chartOptsList);

차트를 그릴 영역을 설정합니다. createDataChart 함수는 여러 페이지에서 쓰이므로 코드의 재사용성을 위해 chartOptsList 객체를 만들어 jquery selector와 차트가 그려질 영역의 width와 height 값을 넘겨줍니다.

 

 

 

function createDataChart(chartOptsList) {
	d3.selectAll("#network-chart svg").remove();
    
    	const width = chartOptsList.width;
	const height = chartOptsList.height;
    	const ajaxUrl = chartOptsList.url;
        const chartSelector = chartOptsList.selector;

createDataChart 내부 모습입니다. 상기 코드에서 작성한 chartOptsList를 파라미터로 받습니다.

차트를 그리기 전에 remove() 메서드를 통해 이전에 그려져 있던 차트를 지우는 로직을 수행해줍니다. 저는 같은 화면에서 선택한 값에 따라 다른 차트가 그려지는 방식이라 해당 코드를 작성했는데요. 화면에 한 번만 차트를 그리는 경우라면 굳이 작성할 필요가 없습니다.

 

 

 


02. ajax 요청으로 데이터 받기

 

const request = $.ajax({
    url: ajaxUrl,
    type: "get",
    withCredentials: true,
    statusCode: {
        200: function (d) {
            if (d.response.nodes.length === 0) {
                createNoDataChart(chartOptsList);
            }

            const links = d.response.links;
            const nodes = d.response.nodes;

ajax 요청으로 차트에 필요한 데이터를 받는 코드입니다. HTTP 200 응답을 받았지만 응답값의 nodes 리스트에 데이터가 없을 경우 데이터가 없을 때의 화면을 보여주는 함수로 넘어갑니다.

데이터가 있을 시에는 links와 nodes 변수에 응답받은 값을 할당해줍니다.

 

{
    "nodes": [
      {
        "id": "11-app",
        "name": "재무시스템",
        "type": "app",
        "service": "11",
        "dst": null,
        "hostId": 13
      },
      {
        "id": "10-host",
        "name": "accountprd",
        "type": "host",
        "service": "11",
        "dst": null,
        "hostId": 10
      },
      {
        "id": "8-host",
        "name": "accountqa",
        "type": "host",
        "service": "11",
        "dst": null,
        "hostId": 8
      }
    ],
    "links": [
      {
        "service": "11",
        "source": "11-app",
        "target": "10-host"
      },
      {
        "service": "11",
        "source": "11-app",
        "target": "8-host"
      }
    ]
}

ajax로 받는 데이터는 위와 같은 형태로 받습니다. nodes 데이터에는 id 값이 필수이고, links 데이터에는 패스가 이어질 노드를 source와 target 필드에 필수로 넣어줘야 합니다. 

 

 


03. svg 위에 link와 node 그리기

    // svg
    const svg = d3
        .select(chartSelector)
        .append("svg")
        .attr("viewBox", [-width / 2, -height / 2, width, height])
        .style("font", "14px Arial")
        .append("g");

chartOptsList 내에 있는 width, height 값을 다시 변수에 할당해주고, svg 변수를 선언해줍니다.

  • select() : 넘겨받은 문자열과 일치하는 DOM의 첫번째 하위 요소를 찾아 선택합니다.
  • append() : 지정된 요소의 마지막 자식 요소를 추가합니다.
  • attr() : 지정된 요소의 특성을 설정합니다.
  • style() : 지정된 요소의 스타일 속성을 설정합니다.

 

상기 메서드 설명에 따라 코드를 해석해보면, chartOptsList.selector에 해당하는 "network-chart" 요소를 선택 후, 해당 요소에 svg 요소를 자식 요소로 추가하고 viewBox 설정으로 크기를 정한 뒤, style을 적용한 것이라고 볼 수 있습니다.

 

 

    // 패스
    const link = svg
        .append("g")
        .attr("fill", "none")
        .attr("stroke-width", 1.5)
        .selectAll("path")
        .data(links)
        .join("path")
        .attr("stroke", "gray");

node간 path를 그려주는 코드입니다.  attr() 메소드로 path의 스타일을 정해주고, 데이터는 ajax 호출로 받은 response의 links 필드를 받습니다.

  • selectAll() : 넘겨받은 문자열과 일치하는 DOM의 모든 하위 요소를 찾아 선택합니다.
  • join() : 데이터와 데이터를 그릴 DOM 요소를 연결합니다. 

 

 

    // 노드 정의
    const node = svg
        .append("g")
        .attr("stroke-linecap", "round")
        .attr("stroke-linejoin", "round")
        .selectAll("g")
        .data(nodes)
        .join("g")
        .call(drag(simulation));

    node
        .append("circle")
        .attr("stroke", "white")
        .attr("stroke-width", 1)
        .attr("r", (d) => radius(d.type))
        .attr("fill", (d) => color(d.type));

    // 노드 아이콘
    node
        .append("svg:foreignObject")
        .attr("x", (d) => iconX(d.type))
        .attr("y", -5.4)
        .attr("height", "11px")
        .attr("width", "13px")
        .style("font-size", "0.2px")
        .html((d) => nodeIcon(d.type));

    // 노드 라벨 추가
    node
        .append("text")
        .attr("x", 11)
        .attr("y", "0.31em")
        .style("font-size", "9px")
        .style("fill", (d) => color(d.type))
        .style("font-weight", 5)
        .text((d) => `[${d.type}]`)
        .clone(true)
        .attr("stroke", "white")
        .attr("stroke-width", 0.3);

node를 정의하는 코드입니다. 

  • append("g") : DOM 요소를 그룹핑합니다. 그룹 내의 모든 요소들에게 동일한 속성을 적용합니다.
  • call() : 지정된 메서드를 한 번 호출합니다.
  • html() : DOM 요소의 기존 하위 요소를 대체합니다.
  • text() : 원하는 text를 입력합니다. 
  • clone() : 선택된 DOM 요소를 복제하여 다음 요소로 삽입합니다.

 

코드를 해석해보면, svg 내의 하위 요소를 그룹핑한 그룹을 만들고,이 그룹을 ajax response 중 nodes 데이터와 연결시키는 것으로 node 변수를 선언하는 것을 알 수 있습니다. 그룹은 차트가 그려질 때 drag 애니메이션 메서드를 한 번 실행합니다. 

node 변수에 append된 원 모형은 데이터의 type에 따라 크기와 색상이 정해집니다. 각 node가 host 타입인지, app 타입인지에 따라 아이콘과 라벨이 달라지는 코드로 볼 수 있습니다.

 

 

다음 포스팅에서는 화면에서 노드를 움직일 수 있는 drag 메서드와 zoom 기능을 구현할 수 있는 zoom 메서드 코드를 공유하겠습니다. 

긴 글 봐주셔서 감사하고 "공감" 한 번만 클릭 부탁드립니다. 감사합니다~! (로그인 없이 클릭할 수 있어요!)