여름이 끝나기 전에 간단한 개인 프로젝트를 해보고 싶어 다시 D3가 필요하게 되었다
저번에 살짝 배워봤지만 D3를 실무에 활용한다거나, 계속 공부해야되는 직접적인 이유 같은건 없는지라
조금 배우고 시간 지나다 보니 다 까먹어버렸는데
좀 더 확실한 개념을 잡고, 이번엔 진짜 프로젝트에 응용해보면 좋을 것 같다. 필요한 지식들만 줍줍해보장
// a클래스에 속한 원을 모두 선택해 빨간색으로 채우고 원의 중점을 svg 영역의 왼쪽에서 100px 떨어진 곳에 위치시키기
d3.selectAll("circle.a").style("fill", "red").attr("cx", 100);
// 웹페이지에 있는 모든 div요소를 빨간색으로 칠하고 클래스를 b로 변경하기
d3.selectAll("div").style("background", "red").attr("class", "b");
// 배열에 있는 요소를 market 클래스의 div 요소에 바인딩(데이터 바인딩)
d3.selectAll("div.market").data([1, 5, 11, 3]);
- 셀렉션을 하려면 먼저 셀렉션을 하고 싶은 요소를 만들어야한다(당연하게도)
- 셀렉션은 일련의 웹 페이지 요소와 이에 대응하는 일련의 데이터다.
- DOM요소보다 많거나 그 반대인 경우가 있는데, 이때 콘텐츠를 생성하는데 요소를 생성하거나 제거하는 메서드를 D3에서 제공
- 로딩 => HTML 요소의 D3 셀렉션 + 바인딩 => 페이지 구조와 모습을 변경 => 구조가 변경되면 사용자가 반응 => 사용자의 반응은 페이지의 콘텐츠를 바뀌게 함
- 웹 페이지의 모든 요소를 같은 추상화 수준에서 처리할 수 있음
거의 같은 결과물을 얻을 수 있는 비슷한 동작을 하는 요소들인데, 차이가 있고
어떤 것을 써야 좋은지는 상황에 따라 다르다
MDN에서는 흥미롭게도 이 둘을 경쟁 기술이라고 표현했음
- 확장 가능한 벡터 그래픽, 2차원 벡터 그래픽을 표현하기 위한 파일 형식
- XHTML과 비슷한 XML 언어로 그래픽을 그리는데 사용함
- 화면이 크거나, 픽셀 수가 적을 경우 렌더링 시간이 canvas보다 빠르다
- 모양 기반, DOM의 일부분이 되는 다중 그래픽 요소(SVG내부의 요소들도 DOM으로 접근 가능)
- 스크립트와 CSS를 통해서도 수정이 가능하다
- 렌더링 영역이 넓은 응용 프로그램에 적합
- 얘도 canvas처럼 그려진 순서가 늦은 친구가 위로 오는것 같음
- 화면이 작고 픽셀 수가 많을때 SVG보다 빠르다
- 픽셀 기반(드로잉 영역에 직접 픽셀을 그리는 느낌)
- canvas 자체가 단일 HTML 요소(내부에 그려지는 요소들을 DOM으로 접근하지 않음)
- 스크립트를 통해서만 수정이 가능함
- 그래픽이 주작업인 게임이 적합
- 대량 데이터를 시각화 할때 더 좋음
- webGL을 사용할 수 있으므로 3D작업도 할 수 있다(SVG와 가장 큰 차이인듯?)
- 상당히 많이 사용한다. 일반적으로 select이후에 오는 메서드들은 특정 처리를 한 새로운 셀렉션을 반환
var someData = ["filter", "filter", "filter", "filter"];
d3.select("body")
.selectAll("div") // => div요소 전체
.data(someData)
.enter()
.append("div")
.html("wow")
.append("span")
.html("even more wow")
.style("font-weight", "900");
- 배열 항목 수가 요소의 개수보다 많다면 enter 메서드로 남은 항목을 어떻게 할 것인지 정의할 수 있다 => 이 예제에서는 div를 새로 추가함
- 배열을 정말 많이 사용한다. 데이터 가공이 필요할 때 역시 배열 메소드를 주구장창 사용한다.
- 로딩 => 포맷 => 측정 => 생성 => 갱신
- 정량적 데이터 : 크기, 위치, 색상 등으로 표현할 수 있으며 정규화가 필요
- 데이터를 불러오는 메소드 : d3.csv, d3.json => 데이터 포맷에 맞는 데이터를 제공함. 이거는 비동기로 실행되므로 요청한 파일이 로딩되기 전에 메서드를 반환. 콜백을 넘길 수 있다. JSON 객체로 로딩된다
- 수치형 데이터가 화면의 위치나 크기에 바로 대응되는 경우는 별로 없고, 일반적으로 d3.scale()을 통해 데이터를 화면에 표기할 수 있도록 정규화함
- linear은 선형 interpolate
const newRamp = d3.scale.linear().domain([5000000, 1300000]).range([0, 500]);
newRamp(1000000); // 20
newRamp(9000000); // 340
newRamp.invert(313); // 역변환
- 데이터 분류(비닝) : 일련의 범위의 값으로 그룹화함으로서 정량적 데이터를 범주로 분류하는 것도 유용
- 배열을 같은 크기의 부분으로 나누어 변위값을 사용할 수 있음
- 비닝은 상자에 담는다는 말.. => 일련의 빈으로 분류하는 변위값 스케일
- 문자열을 리턴하는 것도 가능하다(small, medium, large 이런 식도 가능)
const sampleArray = [423, 124, 66, 424, 58, 10, 900, 44, 1];
const qScale = d3.scale.quantile().domain(sampleArray).range([0, 1, 2]);
qScale(423); // 2
qScale(20); // 0
qScale(10000); // 2
- 데이터를 측정하고 정렬하는 단계. 데이터를 이해하는데 도움이 되는 배열 메서드들을 제공
- 주로 사용하는 메서드 min, max, mean(평균)
const testArray = [88, 10000, 1, 75, 12, 35];
d3.min(testArray, (el) => el);
d3.max(testArray, (el) => el);
d3.mean(testArray, (el) => el);
// 데이터 로딩 이후에 바로 콜백에서 값 반환하기
d3.csv("cities.csv", (data) => {
// 속성을 콜백의 리턴값으로 지정하면 속성의 최소최대평균을 반환한다
d3.main(data, (el) => +el.population);
d3.max(data, (el) => +el.population);
});
// extent는 min과 max를 하나의 배열에 넣어 반환한다
d3.extent(data, (el) => +el.population); // [최소, 최대] 반환
- 셀렉션으로 웹 페이지의 구조와 모습을 바꾼다. 셀렉션은 하나 이상의 DOM 요소로 구성되며 연관된 데이터를 가질 수도 있다.
d3.csv("cities.csv", (error, data) => {
// 로딩이 완료된 데이터를 함수 인자로 넘기기
dataViz(data);
});
// 데이터에 따라 div요소들을 만든다
const dataViz = (incoming) => {
d3.select("body")
.selectAll("div.cities")
.data(incoming)
.enter()
.append("div")
.attr("class", "cities")
.html((d, i) => d.label);
};
- select : 언제나 DOM 요소에 해당하는 CSS 식별자로 d3.select()나 d3.selectAll()을 호출하면서 시작. 이때 식별자에 대응하는 요소가 없는 경우도 있는데 빈 셀렉션에는 보통 enter()메소드로 페이지 안에 들어갈 요소 생성(append)로
- 직관적이지는 않지만 일단 식별자에 대응하는 요소들을 선택을 하고 나서 enter로 append 해줘야함
- data : 선택한 DOM 요소와 배열을 연결시킴. 데이터셋에 들어있는 각 도시는 셀렉션 안의 DOM 요소에 연결되며, 연결된 데이터는 요소의 data 속성에 저장됨(
__data__
)
// 이렇게 접근 가능
document.getElementByClassName("cities")[0].__data__;
- enter, exit : 데이터를 셀렉션에 바인딩할 때 데이터값의 개수가 DOM 요소보다 많거나 적을 수 있다. 데이터가 DOM 요소 수보다 많으면
enter()
을 호출해 셀렉션에 해당 요소가 없는 값을 어떻게 처리해야할지 정의함. 반대로 적으면exit()
을 호출하며 데이터 개수와 DOM 요소 수가 같으면 둘 다 호출이 되지 않는다. - append, insert : 대부분 DOM 요소보다 데이터가 많으므로, 요소를 DOM에 추가하는게 일반적이다. append() 메서드로 요소를 정의하고 추가할 수 있다. insert는 위치를 지정해서 추가할 수 있다
- html : DOM 콘텐츠를 생성한다
- 데이터 양에 따른 사각형 하나만 사용하는 막대 그래프는 단순함. 지방별 인구 통계 데이터나 환자의 의료 데이터 등 대부분 다변량 데이터를 사용한다.
- 다변량 : 각각의 데이터점이 여러 데이터 특성을 가졌음
- 채널 : 도형 하나가 데이터를 시각적으로 표현하는 방법을 전문 용어로 채널이라고 함. 사용하는 데이터에 따라 시각적으로 잘 표현할 수 있는 채널이 다르다
- enter과 exit은 셀렉션에 들어있는 DOM 요소와 셀렉션에 바인딩할 데이터의 수가 일치하지 않을 때 작동한다. 데이터가 DOM보다 많으면 enter()가, 적으면 exit()이 실행된다.
- selection.enter() 메서드에서는 사용할 데이터에 기초해 새로운 요소를 생성하는 방법을 정의하고, selection.exit() 메서드에서는 데이터를 갖지 못하는 기존 요소를 어떻게 삭제할지 정의한다.
- update는 그림요소를 생성했던 함수를 데이터에 다시 적용한다.
- enter나 exit이 발생하면 자식 요소에 적용하는 액션이 일어난다.
- enter에서 append를 사용할 수 있는 것 처럼(데이터가 더 많을 경우 요소 추가), exit에서는 remove를 사용할 수 있다(데이터가 더 적을 경우 요소 삭제)
- 주로 이런 경우에는 초기 데이터 항목에서 사용자의 처리 결과를 반영해 변경된 배열을 다시 바인딩한다. D3에서는 데이터가 바뀐다고 해서 바뀐 데이터가 자동으로 화면에 반영되지 않으며, 직접 화면을 갱신하는 기능을 구현해야 한다. 번거로운 것 같고 처음에 배울때도 그렇게 느꼈지만 이와 같은 작동 방식 덕분에 훨씬 더 큰 융통성을 발휘할 수 있다.
- exit 메서드는 완전히 다른 값인 새로운 배열에 바인딩하는데 사용하려는 것이 아니고, 셀렉션에 바인딩된 배열을 통해 요소를 제거해 페이지를 갱신한다. 요소를 제거하려면 data 메서드가 데이터를 셀렉션에 어떻게 바인딩할지 지정해야 한다.
- 기본적으로는 data()는 해당 데이터값이 배열에서 어디에 위치하는가에 기초에 바인딩 => data의 콜백으로 넘겨주는 것은 binding key로 유니크한 값을 넘겨줘야 한다.
- 데이터를 상당히 많이 변경해 화면을 갱신하려면 바인딩 키에 사용할 객체를 식별한 고유한 ID가 필요하다.
- 이벤트, 색상, 애니메이션 등
// 셀렉션에서 데이터, 노드, 인덱스 접근
d3.select("circle").each((d, i) => {
console.log(d, i, this); // 데이터, 인덱스, 돔노드
});
// 셀렉션에서 바로 돔노드 접근
d3.select("circle").node();
- svg를 사용할때 노드에 내장된 기능을 이용하면 요소를 자식 요소에 다시 추가하거나 할 수 있음
- svg는 z레벨이 없어서 요소를 그리는 순서가 DOM 순서에 의해 결정됨
teamColor = d3.rgb("red");
teamColor = d3.rgb("#ff0000");
teamColor = d3.rgb("rgb(255, 0, 0)");
teamColor = d3.rgb(255, 0, 0);
const ybLamp = d3.scaleLinear()
.interpolate(d3.interpolateLab) // LAB 보간자
.domain([0, maxValue]) // 숫자를 그대로 색상에 맵핑하면 보간된 색상이 나오는데
// 보간자를 사용하지 않으면 구분하기 어려운 색깔이 나올 가능성이 있다
.range(["yellow", "blue"]);
- d3.rgb : 키워드에 맞는 색상을 반환
- darker, brighter : 현재 색상보다 명도가 진한 색, 연한 색 반환(ㄷㄷ)
- 색상 보간자 : HCL, LAB 보간자
- 차트는 그림으로 데이터를 2차원 평면에 배치한 것. 배열에 들어있는 개별값이나 객체인 데이터점에는 범주, 정량적, 위상적, 비구조 데이터가 들어가 있음
- 데이터셋마다 잘 맞는 차트가 있다
- 원시 데이터를 직접 그리거나, 유도된 데이터를 그리는 여러 화면 요소로 구성되어 있다.
- D3가 제공하는 기능은 크게 생성기, 컴포넌트, 레이아웃의 3가지로 분류할 수 있다.
- 생성기 : 요소를 생성. 데이터를 입력받고 데이터에 기초한 화면 객체를 생성하는데 필요한 SVG 그림 코드를 반환한다. => s3.svg.line, d3.svg.area
- 컴포넌트 : path 요소에 들어가는 d 속성 문자열을 생성하는 생성기와 대조적으로 컴포넌트는 특정 차트 컴포넌트를 그리는데 필요한 일련의 화면 객체 생성(이미 대충 만들어져있는 요소) => d3.svg.axis
- 레이아웃 : 일련의 데이터, 그리고 생성기로 구성된 배열을 입력받아 특정 위치와 크기로 그리는 데 필요한 데이터 속성을 동적, 혹은 정적으로 추가한다.(아직은 뭔지 잘 모르겟다)
- 잘 포맷된 축 레이블을 사용하면 의미를 명확히 전달할 수 있음. 데이터를 바인딩하고 요소를 추가해 선, 눈금, 레이블을 축에 추가할 수 있음
- D3에서는 데이터를 표현하는데 사용한 스케일에 기초해 이런 요소들을 쉽게 생성해주는 axis 메서드를 제공
- <path.domain> : 축의 길이와 같은 크기의 축
- <g.tick.major> : 각 눈금의 line과 text 요소를 갖고 있음
- <line.tick.minor> : 작은 눈금마다 생기는 tick 요소
- 이 요소들이 모두 g로 묶인다
- 축을 개선하려면 스타일을 적당히 바꿔준다
- 데이터를 4등분 한 것. 통계의 변량을 도수 분포로 정리하였을 때 적은 것으로부터 1/4, 1/2, 3/4 자리의 변량값.
- d3.scaleQuantile
- D3는 선을 그리는데 사용하는 여러 보간법을 제공한다. 데이터를 더욱 정확하게 표현할 수 있다
- tweetData의 경우 각각의 데이터점이 모든 데이터를 정확하게 나타내므로 선형 기법이 적절하다
- basis, step, cardinal => curve 속성을 사용
- 보간법은 데이터 표현을 조정한다. 시각화된 정보가 현상을 정확히 반영하는지 확인해야 한다.
- 채워진 영역을 그릴 때 line을 사용할 수 있다. 생성한 속성 뒤에 path뒤에 Z를 붙여 경로가 닫혀 있음을 나타낸다
- z를 추가하면 시작점과 끝점을 연결하는 선을 그린다.
- 다른 도형의 꼭대기를 바닥으로 삼아 누적된 도형을 그릴 때는 d3.area를 사용한다. 데이터의 대역을 그리기 좀더 적합하다.