벡터(vector)타입
으로 수학저인 함수 관계로 만든 그래픽 이미지이다 d3.js를 처음 접할 때 svg로 데이터를 시각화 해본 경험이 없어서 되게 생소하였지만, 간단한 요소들을 그려 봄으로써 d3의 컨셉을 잘 이해할 수 있게 되었다.
<script src="https://d3js.org/d3.v7.min.js"></script>
<div id="d3_test"></div>
// svg를 그릴 div 선택
const svg = d3.select('#d3_test')
.append('svg')
.attr('width','100%')
.attr('height','100vh')
.style('background-color','pink')
// circle 추가 !!
svg.append('circle')
.attr('fill','blue')
.attr('r', 30)
.attr('cx', 35)
.attr('cy', 35)
// rect 추가 !!
svg.append('rect')
.attr('fill', 'red')
.attr('x', 70)
.attr('y', 50)
.attr('rx', 5)
.attr('width', 40)
.attr('height', 40)
// line 추가 !!
svg.append('line')
.attr('x1', 10)
.attr('x2', 60)
.attr('y1', 60)
.attr('y2', 90)
.attr('stroke', 'yellow')
별 다른 설명을 안해도
svg
요소에 circle
, rect
, line
을 추가하는구나~ 라고 생각 할 수 있다. 개인적인 생각으론 이 개념이 절반을 먹고 들어가는 것 같다.
여기에 하나의 객체에 연속적으로 메서드를 호출하고 메서드가 실행된 후 객체 자신을 반환하는 method chaining
개념도 들어간다
데이터를 시각화 하다보면 내가 만든 도형들을 grouping 해야하는 경우가 있다. 이때 사용하는게 <g>
요소이다
<g>
는 공간으르 따로 차지 않고, 요소들을 논리적으로 그룹화한다.
예를 들어 circle
안에 text
가 있다고 할 경우 따로 따로 생성하고 움직이는 것 보다 두개를 하나의 group으로 묶어서 이용하는게 더 편리하다.
const group1 = svg.append('g')
// circle 추가 !!
group1.append('circle')
.attr('fill','yellow')
.attr('r', 30)
.attr('cx', 35)
.attr('cy', 35)
group1.append('text')
.text('input text')
.attr("x",'10')
.attr("y",'35')
.style("font-size",'12px')
하지만 <g>
요소를 이동하려면 transform
속성을 조정해야한다.
현재 d3.js를 이용하여 세계/국내 지도 위에 데이터를 시각화 하는 업무를 하다보니 데이터 정규화작업이 중요하게 다가왔다.
표출해야하는 화면의 규격은 작고, 데이터는 크고.. 만약 sacle을 조절하지 않으면 도형으로 화면이 꽉 찼을 것이다.
범위 안에 있는 값들을 선형으로 scale 조절 하는 것
쉽게 이야기하면 주어지는(input 되는) 데이터의 범위(domain)
를 사용자 범위(range)
이내로 줄이는 것이다.
scalesLineae()
의 경우 function을 return 하기에 값을 전달해주어야 한다.const linear = d3.scaleLinear().domain([300(min), 1800(max)]).range([10(최소크기), 100(최대크기)]);
console을 찍어보면 function이 반환 됨
circle
을 그릴 데이터 (min : 300, max : 1800) const data = [
{x: 15, y: 40, r: 300, color: '#ffdde1'},
{x: 50, y: 40, r: 600, color: '#304352'},
{x: 90, y: 40, r: 900, color: '#d7d2cc'},
{x: 15, y: 90, r: 1200, color: '#CCCCB2'},
{x: 50, y: 90, r: 1500, color: '#3498db'},
{x: 90, y: 90, r: 1800, color: '#ee9ca7'},
]
circle
그리는 코드 (with. scaleLinear()) const linear = d3.scaleLinear().domain([300, 1800]).range([10, 100]);
svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('fill', d => d.color)
// -----------------------------
// linear를 안해주면 화면을 가득 채운다.
// .attr('r', d => d.r)
.attr('r', d => linear(d.r))
// -----------------------------
.attr('cx', d => d.x * 3)
.attr('cy', d => d.y * 3)
d3에서는 셀렉션으로 웹 페이지으 구조를 바꾼다. 셀렉션은 하나 이상의 DOM 요소로 구성되며 연관된 데이터를 가질 수도 있다.
데이터를 바인딩 하는 방법에 대해서 header를 달아가며 설명하기 어려운 것 같아서 구구절절 정리를 해보려고 한다.
select()
은 하나의 요소만 선택selectAll()
은 모든 요소 선택예시 상황
<div class="container"> <div class="test_div" id="id1"></div> <div class="test_div" id="id2"></div> <div class="test_div" id="id3"></div> <div class="test_div" id="id4"></div> </div>
const data = ['test1', 'test2', 'test3', 'test4', 'test5'];
const select = d3.select('#id2')
.data(data)
.append('text')
.text(d => d)
const data = ['test1', 'test2', 'test3', 'test4', 'test5'];
const selectAll = d3.selectAll('.test_div')
.data(data)
.append('text')
.text(d => d)
append()
를 이용해서 추가하면 된다.
enter()
- data 배열의 내용물은 다섯개 이고 (데이터 개수)
container
내부에<div>
는 네 개 이다. (dom 개수)데이터를 select 하여 biding 할 때 데이터의 개수와 dom의 개수가 차이가 날 때 어떻게 처리할 수 있을까???
enter() ??
데이터가 dom보다 더 많아 잔여 데이터를 어떻게 처리할 지 정의한다.enter() 하기전
먼저 enter()를 하지 않으면 4개의 dom에만 text가 작성된다.const data = ['test1', 'test2', 'test3', 'test4', 'test5']; const select = d3.select('.container') .selectAll('div') .data(data) .append('div') .text(d => d)
중간 결과
enter() 적용 시
새로 변수를 호출해서data binding
을 해주고,enter()
를 한 뒤 잔여 데이터 에 대해 어떻게 할건지 정의 해주었다.const addEnter = select .data(data) .enter() .append('h1') .text(d => d);
- enter() 결과
updata
는 다양한 의미가 있을 거 같은데, 아래에서는 1.데이터 변경
과 2. 로직 실행 시 데이터 update(?)
두가지에 대해서 설명하고 있다.
d3.js에는 update
함수가 따로 없다. 그렇기에 dom 내부를 지운 뒤 다시 그리는 방식으로 진행하고 있다.
내부를 지우는 방법도 동일하게 원하는 dom 선택 후 remove
를 해주면 된다.
const remove = d3.select('.container')
.selectAll('div')
.remove();
remove
결과배열(Array)혹은 JSON 형태의 데이터를 다룰 경우 특정 컬럼 값에 대해서 연산을 해줄 필요가 있다. 보통 데이터에 대한 연산은 외부에서 선행되는 경우가 일반적이겠지만, 불가피한 경우 로직에서 구현하기도 한다.
그때는 each()
를 이용해보자.
const each = d3.select('.container')
.selectAll('div')
.data(data)
.each((d,i) => console.log('each index = %o , data = %s ',i,d));
d3는 자유도가 높은 만큼 사용자가 모든 걸 다 설정해야하기 때문에 사용하기 번거로운 라이브러리인거 같다. 특히나 지도를 다루다 보니 안그래도 어려운 거에 어려운게 더해져서 겁나 어렵다..
아무튼 d3.js를 사용하면서 헤깔리고 정리를 하면 좋을거 같은 부분들을 간단하게 정리해보았다.
이렇게 정리해보니 확실히 이해도가 많이 향상되었다,