Learn to Create D3.js Data Visualizations by Example
원본 : https://www.sitepoint.com/d3-js-data-visualizationsD3 공부하기 좋은 자료 인거 같아 개인적으로 쓸 요량으로 번역하였습니다. 링크 또는 다른곳에 옮기지 말아 주시기 바랍니다.^^
나는 HTML의 humble bar chart로 만드는 것은 document의 D3 transforms data를 이해하는 가장 쉬운 방법이며 William Playfairs charts보다 더 좋은 것을 약속한다. 여기를 보자.
d3.select('#chart') .selectAll("div") .data([4, 8, 15, 16, 23, 42]) .enter() .append("div") .style("height", (d)=> d + "px")
selectAll 함수는 D3 “selection”을 return 한다 : elements의 array은 우리는 enter와 append는 각 데이터 point위해 div 만들었다.
이 code maps은 [4, 8, 15, 16, 23, 42]은 들어오고, HTML로 나온다.
<div id="chart"> <div style="height: 4px;"></div> <div style="height: 8px;"></div> <div style="height: 15px;"></div> <div style="height: 16px;"></div> <div style="height: 23px;"></div> <div style="height: 42px;"></div> </div>
변경되지 않은 style properties의 모든 것은 CSS에서 할 수 있다.
#chart div { display: inline-block; background: #4285F4; width: 20px; margin-right: 3px; }
Github’s Contribution Chart
코드 몇개를 추가하면 우리는 Github와 비슷한 contribution chart를 bar chart로 만들 수 있다.
const colorMap = d3.interpolateRgb( d3.rgb('#d6e685'), d3.rgb('#1e6823') ) d3.select('#chart') .selectAll("div") .data([.2, .4, 0, 0, .13, .92]) .enter() .append("div") .style("background-color", (d)=> { return d == 0 ? '#eee' : colorMap(d) })
colorMap 함수는 0과 1 사이의 값을 입력하고, 제공하는 두가지 사이에 컬러 그라데이션중 하나를 return한다. Interpolation은 programming과 animation의 키 도구 이다. 우리는 나중에 더 많은 예제에서 볼것이다.
D3의 힘의 대부분은 SVG롤 작업 하는 것에서 진심으로 나온다. circles, polygons, paths text와 같은 2D graphics를 그리기 위한 태그를 포함하는 것
<svg width="200" height="200"> <circle fill="#3E5693" cx="50" cy="120" r="20" /> <text x="100" y="100">Hello SVG!</text> <path d="M100,10L150,70L50,70Z" fill="#BEDBC3" stroke="#539E91" stroke-width="3"> </svg>
circle는 좌표 50,120에 20의 반지름이다.
text는 좌표 100,100에 “Hello SVG”이다.
triangle는 3px의 선에 다음과 같은 instruction attribute이다.
<path>는 SVG의 대부분 파워풀한 엘리먼트이다.
이전의 예제의 데이터 설정은 간단한 숫자의 배열을 가지고 했다. D3는 또한 더 복잡한 타입으로 작업을 할 수 있다.
const data = [{ label: "7am", sales: 20 },{ label: "8am", sales: 12 }, { label: "9am", sales: 8 }, { label: "10am", sales: 27 }]
각 데이터의 포인트는 #chart에 (group) element를 추가하고, 를 추가하고, 우리의 객체에서 속성들을 <text> elements에 추가할 것이다.
const g = d3.select('#chart') .selectAll("g") .data(data) .enter() .append('g') g.append("circle") .attr('cy', 40) .attr('cx', (d, i)=> (i+1) * 50) .attr('r', (d)=> d.sales) g.append("text") .attr('y', 90) .attr('x', (d, i)=> (i+1) * 50) .text((d)=> d.label)
변수 g는 <g> node의 배열을 포함한 d3”selection”을 잡고, selection에서 각 아이템을 새로운 element에 추가하는 append()와 같은 활동이다.
SVG에서 line chart를 그리는 것은 굉장히 간단하다. 우리는 아래와 같이 데이터를 transform을 원한다.
const data = [ { x: 0, y: 30 }, { x: 50, y: 20 }, { x: 100, y: 40 }, { x: 150, y: 80 }, { x: 200, y: 95 } ]
<svg id="chart" height="100" width="200"> <path stroke-width="2" d="M0,70L50,80L100,60L150,20L200,5"> </svg>
y의 값은 SVG는 top이 값이 0이기 때문에 원하는 값으로 y를 설정하려면 100에서 원하는 숫자를 빼면 원하는 곳에 표시가 된다.
단순한 path elements이다. 우리는 이와 같은 코드를 우리 자신이 만들 수 있다.
const path = "M" + data.map((d)=> { return d.x + ',' + (100 - d.y); }).join('L'); const line = `<path stroke-width="2" d="${ path }"/>`; document.querySelector('#chart').innerHTML = line;
D3는 이것을 단순하게 만드는 path generating 함수가 있다. 아래를 보자
const line = d3.svg.line() .x((d)=> d.x) .y((d)=> 100 - d.y) .interpolate("linear") d3.select('#chart') .append("path") .attr('stroke-width', 2) .attr('d', line(data))
Scales는 domain을 넣고 range가 나오는 함수이다.
에제에서 우리가 지금까지 본것은 경계가 고정적인것이다. 하지만 그래프의 경계가 고정적이지 않고 변경이 된다면, 차트도 경계에 맞게 포지션이 변경 되어야 한다.
다음의 데이터를 500x200에서 line chart로 렌더링 해본다면
const data = [ { x: 0, y: 30 }, { x: 25, y: 15 }, { x: 50, y: 20 } ]
d3.max를 이용하여 최대값을 찾는 것이다. 또한 domain은 이러한 최대값을 사용하여 차트 경계가 변경 되었을때, 거기에 맞게 차트를 변경한다.
const width = 500; const height = 200; const xMax = d3.max(data, (d)=> d.x) const yMax = d3.max(data, (d)=> d.y) const xScale = d3.scale.linear() .domain([0, xMax]) // input domain .range([0, width]) // output range const yScale = d3.scale.linear() .domain([0, yMax]) // input domain .range([height, 0]) // output range
아래는 실제 값과 경계 안에서의 값을 보여준다.
xScale(0) -> 0 xScale(10) -> 100 xScale(50) -> 500
xScale(-10) -> -100 xScale(60) -> 600
이러한 것을 우리는 line generating function을 에서 scales를 이용할 수 있다.
아래는 경게면 안에 padding을 주는 방법이다.
const padding = 20; const xScale = d3.scale.linear() .domain([0, xMax]) .range([padding, width - padding]) const yScale = d3.scale.linear() .domain([0, yMax]) .range([height - padding, padding])
지금 우리는 동적인 데이터 셋으로 렌더링 할수 있고, 우리의 line chart에 500px / 200px의 경계에 안에 모두 20px padding을 넣을 것이다.
Linear scales는 대부분은 공토의 타입이다. 하지만 exponential scalesd의 POW와 names 또는 categories와 같은 representing non-numeric data ordinal scales 같은 다른 것이 있다. 추가적으로 Quantitative Scales와 Ordinal Scales 그리고 date ranges mapping을 위한 Time Scales도 있다.
예를 들어 우리는 0부터 500까지 사이의 숫자에서 나의 lifespan maps의 scale를 만들 수 있다.
const life = d3.time.scale() .domain([new Date(1986, 1, 18), new Date()]) .range([0, 500]) // At which point between 0 and 500 was my 18th birthday? life(new Date(2004, 1, 18))
Animated Flight Visualization
우리는 잘하고 좋았지만 지금까지 우리는 고정적인 생동감이 없는 그래픽만 했다. visualization 한 에니메이션을 만드어 보자 Australia에서 Melbourne와 Sydney를 활발하게 날라 다는 것을 보여주자
graphic의 이 타입에서 SVG document는 text, lines 그리고 circles로 만들어져 있다.
<svg id="chart" width="600" height="500"> <text class="time" x="300" y="50" text-anchor="middle">6:00</text> <text class="origin-text" x="90" y="75" text-anchor="end">MEL</text> <text class="dest-text" x="510" y="75" text-anchor="start">SYD</text> <circle class="origin-dot" r="5" cx="100" cy="75" /> <circle class="dest-dot" r="5" cx="500" cy="75" /> <line class="origin-dest-line" x1="110" y1="75" x2="490" y2="75" /> <!-- for each flight in the current time --> <g class="flight"> <text class="flight-id" x="160" y="100">JQ 500</text> <line class="flight-line" x1="100" y1="100" x2="150" y2="100" /> <circle class="flight-dot" cx="150" cy="100" r="5" /> </g> </svg>
동적인 부분은 항공기 그룹안의 시간과 element이고 데이터는 이와 같이 볼수 있다.
let data = [ { departs: '06:00 am', arrives: '07:25 am', id: 'Jetstar 500' }, { departs: '06:00 am', arrives: '07:25 am', id: 'Qantas 400' }, { departs: '06:00 am', arrives: '07:25 am', id: 'Virgin 803' } ]
동적인 시간을 위한 x position을 얻고 우리는 우리의 차트에서 x position에 출발시간과 도착시간 각 비행기를 위한 time scale 만드는 것이 필요하다. 우리는 Date 객체와 scales를 추가하길 시작하면서 우리의 데이터를 통해 반복할 수 있다. Moment.js는 date parsing 과 manipulatio를 여기많이 도울수 있다.
data.forEach((d)=> { d.departureDate = moment(d.departs, "hh-mm a").toDate(); d.arrivalDate = moment(d.arrives, "hh-mm a").toDate(); d.xScale = d3.time.scale() .domain([departureDate, arrivalDate]) .range([100, 500]) });
우리는 각 항공기를 조정하기 위해 x를 얻기 위한 xScale()를 우리의 변화된 Date를 통과 할 수 있다.
출발과 도착 시간은 5분동안 돌고 그래서 우리는 첫번째 출발과 마지막 도착에서 5m증가하는 통해 단계를 할 수 있다.
let now = moment(data[0].departs, "hh:mm a"); const end = moment(data[data.length - 1].arrives, "hh:mm a"); const loop = function() { const time = now.toDate(); // Filter data set to active flights in the current time const currentData = data.filter((d)=> { return d.departureDate <= time && time <= d.arrivalDate }); render(currentData, time); if (now <= end) { // Increment 5m and call loop again in 500ms now = now.add(5, 'minutes'); setTimeout(loop, 500); } }
D3는 다음과 같은 경우 일때 엘리먼트의 transformation과 transitions가 명시되어 있다.
New data points come in (Enter)
Existing data points change (Update)
Existing data points are removed (Exit)
const render = function(data, time) { // render the time d3.select('.time') .text(moment(time).format("hh:mm a")) // Make a d3 selection and apply our data set const flight = d3.select('#chart') .selectAll('g.flight') .data(data, (d)=> d.id) // Enter new nodes for any data point with an id not in the DOM const newFlight = flight.enter() .append("g") .attr('class', 'flight') const xPoint = (d)=> d.xScale(time); const yPoint = (d, i)=> 100 + i * 25; newFlight.append("circle") .attr('class',"flight-dot") .attr('cx', xPoint) .attr('cy', yPoint) .attr('r', "5") // Update existing nodes in selection with id's that are in the data flight.select('.flight-dot') .attr('cx', xPoint) .attr('cy', yPoint) // Exit old nodes in selection with id's that are not in the data const oldFlight = flight.exit() .remove() }
이 코드는 5분간 500ms frame을 render한다.
모든 비행기 원에서 새 비행기 그룹을 만든다.
이 작업은 그러나 무엇이 우리는 정말 원하는지 부드러운 transition을 프레임들의 각 사이를. 우리는 D3 selection으로 transition을 만드는 것을 달성할 수 있고 속성과 스타일 속성을 설정하기전에 easing function와 duration을 제공한다.
예는, 비행기 그룸들의 투명도를 페이드 하는 것이다.
const newFlight = flight.enter() .append("g") .attr('class', 'flight') .attr('opacity', 0) newFlight.transition() .duration(500) .attr('opacity', 1)
flight.exit() .transition() .duration(500) .attr('opacity', 0) .remove()
x 와 y 포인트 사이에 부드러운 transition을 추가
flight.select('.flight-dot') .transition() .duration(500) .ease('linear') .attr('cx', xPoint) .attr('cy', yPoint)
우리는 5분 증가 사이에 또한 transition을 할 수 있고, tween 함수를 사용하여 매 5분 보다 매분을 display 할수 있다.
const inFiveMinutes = moment(time).add(5, 'minutes').toDate(); const i = d3.interpolate(time, inFiveMinutes); d3.select('.time') .transition() .duration(500) .ease('linear') .tween("text", ()=> { return function(t) { this.textContent = moment(i(t)).format("hh:mm a"); }; });
t는 0과 1 사이의 transition이 진행중인 값이다.
이 아티클보다 더 많은 라이브러리가 있다. 몇가지 예제중에 dig를 가자 좋아 하지만, 아래의 것을 적어 놓자
D3 Gallery에는 더 많은 예제가 있다. 만약에 배우길 원한다면 Scott Murray’s D3 Tutorials와 D3 documentation을 이용하길 바란다.