D3.js 산포도 그래프를 단계별 그리며 설명 - scaleLinear(), domain(), range(), axisBottom()

산포도 그래프를 각각의 단계별로 그려 보는 방법에 대해서 알아본다.

산포도 그래프 작성 방법

간단한 그래프 샘플로 산포도를 그려보자. 만듭니다.

산포도(散布度) 또는 변산도(變散度)는 변량이 흩어져 있는 정도를 하나의 수로 나타낸 값이다.

좌표점 찍기

이번에는 다음 데이터를 사용한다.

var dataset = [ [5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
                [410, 12], [475, 44], [25, 67], [85, 21], [220, 88] ];

[ ]는 배열로, [[x좌표1, y좌표1], [x좌표2, y좌표2], ...]와 같은 형식의 2차원 배열의 데이터이다.

우선, 좌표를 원을 표시하는 “circle"를 사용해 표시해 보겠다.

svg.selectAll("circle") 
  .data(dataset) 
  .enter()
  .append("circle")
  .attr("cx", function(d) { return d[0]; })
  .attr("cy", function(d) { return d[1]; })
  .attr("r", 4);

전체 코드는 아래와 같다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>D3 Test</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
  </head>
  <body>
    <script>
      var svg = d3.select("body").append("svg").attr("width", 700).attr("height", 100);
      var dataset = [ [5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
                      [410, 12], [475, 44], [25, 67], [85, 21], [220, 88] ];

      svg.selectAll("circle") 
        .data(dataset) 
        .enter()
        .append("circle")
        .attr("cx", function(d) { return d[0]; })
        .attr("cy", function(d) { return d[1]; })
        .attr("r", 4);
    </script>
  </body>
</html>

“circle"에 할당한 데이터는 2차원 배열이기 때문에 “circle"에는 [x 좌표, y 좌표]의 데이터가 할당된다. 예를 들면, 첫 번째의 “circle"에는 [5, 20]이 할당된다. 이 데이터는 cx,cy 속성에 각각 설정하고 있다.

좌표점 텍스트 표시

다음으로는 좌표 위치를 x 좌표, y 좌표의 형식으로 텍스트로 표시해 보겠다.

svg.selectAll("text") 
  .data(dataset) 
  .enter()
  .append("text")
  .attr("x", function(d) { return d[0]; })
  .attr("y", function(d) { return d[1]; })
  .attr("fill", "red")
  .text(function(d) {
    return d[0] + "," + d[1];
  });

전체 코드는 아래와 같다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>D3 Test</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
  </head>
  <body>
    <script>
      var svg = d3.select("body").append("svg").attr("width", 700).attr("height", 100);
      var dataset = [ [5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
                      [410, 12], [475, 44], [25, 67], [85, 21], [220, 88] ];

      svg.selectAll("circle") 
        .data(dataset) 
        .enter()
        .append("circle")
        .attr("cx", function(d) { return d[0]; })
        .attr("cy", function(d) { return d[1]; })
        .attr("r", 4);

      svg.selectAll("text") 
        .data(dataset) 
        .enter()
        .append("text")
        .attr("x", function(d) { return d[0]; })
        .attr("y", function(d) { return d[1]; })
        .attr("fill", "red")
        .text(function(d) {
          return d[0] + "," + d[1];
        });
    </script>
  </body>
</html>

x 좌표는 오른쪽을 향하며 증가하지만, y 좌표는 아래를 향하며 증가하고 있다는 것을 주위해야 한다.

스케일 변환

스케일의 변환에는 다음의 D3의 함수를 사용한다.

var scale = d3.scaleLinear()
    .domain([0, 500])
    .range([0, 100]);

이 함수는 domain으로 지정한 범위를 range로 지정한 범위로 변환하는 함수를 scale라는 변수로 지정한다.

예를 들어, 아래와 같이 호출할 수 있는데, 호출을 하게 되면 20이 반환된다.

scale(100);

이는 [0,500]의 범위를 [0,100]의 범위로 변경, 즉, 좌표를 1/5로 한 결과이다. (100/5 = 20)

이것을 그래프에 적용해보자.

그래프의 너비와 높이를 아래와 같이 설정한다.

var width = 400;
var height = 300;

비율을 4:3으로 설정하였다.

var xScale = d3.scaleLinear()
  .domain([0, d3.max(dataset, function(d) { return d[0]; })])
  .range([0, width]);

var yScale = d3.scaleLinear()
  .domain([0, d3.max(dataset, function(d) { return d[1]; })])
  .range([height, 0]);

여기서 데이터의 최대치를 구하기 위해서 d3.max(..)를 사용하였다. d3.max는 첫 번째 인수로 배열을 지정하고 두 번째 인수로 반환하고 함수로 지정할 수 있다.
y 좌표는 반대로 하려고 하기 때문에, range의 첫 번째 인수에 큰 값을 넣었다.

이 함수를 사용하여 다시 그린다.

  svg.selectAll("circle") 
     .data(dataset) 
     .enter()
     .append("circle")
     .attr("cx", function(d) { return xScale(d[0]); })
     .attr("cy", function(d) { return yScale(d[1]); })
     .attr("r", 4);

전체 코드는 아래와 같다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>D3 Test</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
  </head>
  <body>
    <script>
      // 여기에 코드를 작성한다.
      var dataset = [ [5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
                [410, 12], [475, 44], [25, 67], [85, 21], [220, 88] ];

      var width = 400;
      var height = 300;

      var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);

      var xScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function(d) { return d[0]; })])
        .range([0, width]);

      var yScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function(d) { return d[1]; })])
        .range([height, 0]);

      svg.selectAll("circle") 
        .data(dataset) 
        .enter()
        .append("circle")
        .attr("cx", function(d) { return xScale(d[0]); })
        .attr("cy", function(d) { return yScale(d[1]); })
        .attr("r", 4);

      svg.selectAll("text") 
        .data(dataset) 
        .enter()
        .append("text")
        .attr("x", function(d) { return xScale(d[0]); })
        .attr("y", function(d) { return yScale(d[1]); })
        .attr("fill", "red")
        .text(function(d) {
          return d[0] + "," + d[1];
        });
    </script>
  </body>
</html>

y 좌표는 아래쪽이 작아지고, 종횡비도 4:3이 되었다.

축 그리기

다음으로 축을 그려보자. 여기에는 D3의 함수가 준비되어 있다.

var axisX = d3.axisBottom(xScale);
svg.append("g")
  .call(axisX);

앞에서의 scale과 함께 사용한다. 아래쪽 축을 나타내는 d3.axisBottom외에도 d3.axisTop, d3.axisRight, d3.axisLeft가 있다. 이것을 call 함수로 호출하면 아래와 같이 축을 그려준다.

위에 코드를 적용한 전체 코드는 아래와 같다. 축이 있기에 text 표시는 지웠다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>D3 Test</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
  </head>
  <body>
    <script>
      var dataset = [ [5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
                [410, 12], [475, 44], [25, 67], [85, 21], [220, 88] ];

      var width = 400;
      var height = 300;

      var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);

      var xScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function(d) { return d[0]; })])
        .range([0, width]);

      var yScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function(d) { return d[1]; })])
        .range([height, 0]);

      svg.selectAll("circle") 
        .data(dataset) 
        .enter()
        .append("circle")
        .attr("cx", function(d) { return xScale(d[0]); })
        .attr("cy", function(d) { return yScale(d[1]); })
        .attr("r", 4);

      var axisX = d3.axisBottom(xScale);
      svg.append("g")
        .call(axisX);
    </script>
  </body>
</html>

좌표점과 축 위치 조정

위치와 축을 표시하는 너비를 고려하지 않다. 그래서 다음과 같이 위치를 이동한다.

var axisX = d3.axisBottom(xScale);
var padding = 30;
svg.append("g")
    .attr("transform", "translate(" + 0 + "," + (height-padding) + ")")
    .call(axisX);

이것을 고려하면 그래프를 그리는 코드는 아래와 같이 된다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>D3 Test</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
  </head>
  <body>
    <script>
     var dataset = [ [5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
                      [410, 12], [475, 44], [25, 67], [85, 21], [220, 88] ];

      var width = 400;
      var height = 300;
      var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);

      var padding = 30;
      var xScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function(d) { return d[0]; })])
        .range([padding, width - padding]);

      var yScale = d3.scaleLinear()
        .domain([0, d3.max(dataset, function(d) { return d[1]; })])
        .range([height - padding, padding]);

      var axisx = d3.axisBottom(xScale);
      var axisy = d3.axisLeft(yScale);
      svg.append("g")
        .attr("transform", "translate(" + 0 + "," + (height - padding) + ")")
        .call(axisx);

      svg.append("g")
        .attr("transform", "translate(" + padding + "," + 0 + ")")
        .call(axisy);

      svg.selectAll("circle") 
        .data(dataset) 
        .enter()
        .append("circle")
        .attr("cx", function(d) { return xScale(d[0]); })
        .attr("cy", function(d) { return yScale(d[1]); })
        .attr("fill", "SkyBlue")
        .attr("r", 4);
    </script>
  </body>
</html>

이것으로 산포도가 완성되었다. 원의 색도 하늘색으로 변경해 봤다.




최종 수정 : 2024-01-18