과제 리포지토리에서 프로젝트를 fork한 뒤 내 branch만 남기고 삭제하였다.
그 후 사용하던 notion-clone
이라는 리포지토리에 clone
땡기고 push mirror
명령어를 이용하여 커밋내역째 push함.
이후 사태 발생.
과제 리포지토리 브랜치가 다 삭제됨...
처음에는 모든 pr이 닫혔길래 버그인 줄 알았으나 브랜치가 모두 삭제되었다는 사실을 알게됨.
다행히 restore
를 할수있어서 브랜치들을 복구했으나 총 7분의 브랜치가 복구되지 않는 사실 발생
7분의 공통점을 찾아본 결과 fork하신 분들의 브랜치였음. 여쭤보니 다행히 7분중 6분은 fork하신 브랜치가 최신이라고 하셨다.
남은 한분의 답변만 기다리는중...😥
내가 100%알기 전까지 안다고 생각하지말자...중요한건 어린아이에게 설명할수 있는 수준까지 공부해놓자...!!
데이터의 타입은 다양하다. text, xml, csv, tsv, json... D3는 생각보다 더 많은 종류의 데이터를 지원한다.
d3.데이터타입('주소')
이렇게 가져오면 데이터를 잘 가져와준다. 어떻게 가공해서 사용할지는 순전히 개발자의 몫이다.
참고로 tsv,csv
는 가공이 잘 되어있다면[{},{}...]
형태로 잘 가져와 주는 듯 보인다.
selection
객체 메서드중 DOM조작과 비슷한 기능이 아닌 D3만의 고유한 메서드 중 하나! 사실상 핵심이라고 보면 된다
enter와 update 그리고 exit를 사용한다.
=> data를 넣고, 업데이트하고 데이터를 제거(시야에서 사라지게 함)한다.
이 세가지를 아우르는 메서드가 selection.join(enter,[,update][,exit])
이다.
참고로 join
메서드는 요소를 정렬하여 반환해준다.
svg요소가 없는 상태에서도 data를 먼저 추가해놓을수 있다. 만약 rect가 없는 상황에서도...
//rect가 없을땐 null을 반환한다. 하지만 여기에 먼저 data를 체이닝해놓을 수 있다.
svg.selectAll('rect').data(someData)
이렇게 data를 먼저 달아놓고 svg를 생성할 수도 있다 ㅎㅎ
svg.selectAll('rect').data(someData).join('rect')
이러면 이제 데이터가 바인딩된 빈 객체에 svg요소인 rect가 추가된다. 이게 3개중 하나인 enter
이다.
아래처럼 함수도 전달할 수 있다.
svg.selectAll('rect').data(someData).join((enter) => enter.append('rect').attr('fill','green'))
//3개의 초록색 rect가 추가됨
svg.selectAll('rect').data(threeData).join(
(enter) => enter.append('rect').attr('fill','green'),
)
//3개의 초록색 rect에 1개의 주황색 rect가 추가됨 = 4개
svg.selectAll('rect').data(fourData).join(
(update) => update.attr('fill').attr('fill','orange'),
)
//4개중 2개의 rect를 파란색으로 바꾼 뒤 삭제함
svg.selectAll('rect').data(twoData).join(
(exit) => exit.attr('fill','blue').remove(),
)
또한 for문도 대체할 수 있다.
someEl.attr('x', (d,i) => d * foo).text((d,i) => !!d && i*2);
함수의 인자로 data, index
가 들어오나보다!
selection 객체도 객체다. 객체는 불변성을 유지해야 추적도 쉽고 관리하기 쉽다.
selection.data(fourData, (d) => d.name);
위처럼 두번째 인자로 키 값을 설정하는 함수를 전달할 수 있다. 기본값은 데이터의 인덱스다.
리액트의 key
프로퍼티와 비슷하다. 각 데이터를 구분하기 위해 존재한다.
const x = d3.scaleLinear();
x.domain([0,10]);
x.range([0,100]);
x(0) // 0
x(10) //100
정의역(domain)에 10을넣고, 범위가 0~100이다. 따라서 y = x * 10
의 함수가 된다.
이건 어디에 쓸까? 바로 척도의 비율을 맞출때 사용한다.
예를들어 정량적 데이터의 최소값이 1632고 최댓값이 4890이라고 생각해보자.
참고로 y축은 0부터 시작하는게 좋다고 하셨으니 이런 비율을 만들 수 있을 것이다.
const x = d3.scaleLinear()
.domain([0, d3.max(data, (d) => d.value)]) //데이터 중 최댓값(4890)을 정의역의 최댓값으로 둔다
.range([0, width]) //보여주고싶은 너비가 치역의 최댓값이다.
이러면 y = x * (width/데이터의 값)
의 비율이 나오게 된다.
//순서형 척도
const ordinal = d3.scaleOrdinal()
.domain([1,2,3,4,5]) //정의역의 각 인덱스가 치역의 각 인덱스와 매핑된다.
.range([6,7,8,9,10]);
ordinal(1) // 6
ordinal(3) // 8
//묶음 척도
const band = d3.scaleBand()
.domain([1,2,3,4,5]) //치역 범위를 doamin의 길이로 나눈값을 하나씩 가지게된다
.range([1,100]);
band(1) // 1 시작점을 반환한다
band(5) // 80.2
band.bandwidth()// 19.8
g태그 기준으로 너비, 높이 정하고 margin을 준다.
const margin = {top:30, right:50, bottom:0, left:150};
const width= 400;
const heigth = 170
const svgGroup = body.append('svg')
.attr('width', width+margin.left+margin.right);
.attr('height', height + margin.top + margin.bottom);
//아래부터는 g태그를 더하고 g태그의 위치를 옮기는 코드다 헷갈리지 말자
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
//x축
const xAxis = d3.axisTop(x) //x는 선형스케일에서 제작한 x값이다
.ticks(4) //4개의 틱으로 나눈다
.tickFormat((d,i) => {return `${!!d ? Math.floor(d/10000) : d}만명`}); //나눈 틱에 붙어있는 텍스트
svgGroup.append('g').call(xAxis);
//y축
const yAxis = d3.axisLeft(y) //y는 밴드스케일에서 제작한 y값이다
svgGroup.append('g').call(yAxis);
이쯤되면 selection
객체가 왜 필요한 지 알것 같다. 데이터를 가지고 있으므로 메서드 내부에서 (d,i)
이런식으로 데이터와 인덱스를 활용할 수 있다. 뿐만 아니라 척도나 축 등을 직접 설정할 수도 있다. 생각보다 훨씬 자세하게 구현이 가능할 것 같음!