나의 스킬 스택을 Data로 활용해 Tree 구조의 차트로 만들어 보자
Meterialize를 사용해 기본적인 앱의 틀을 잡아주고 모달을 띄워 데이터를 추가할 수 있게 한다.
Firebase에 데이터를 저장해 실시간으로 반영하려고 한다.
HTML에 input과 firebase를 연결해주고
그래프를 그리는 js 파일에서 firebase data를 불러와서 적용한다.
graph.js
// data & firebase
let data = [];
db.collection("stacks").onSnapshot(res => {
// 실시간 데이터 업데이트
res.docChanges().forEach(change => {
const doc = {
...change.doc.data(),
id: change.doc.id
};
switch (change.type) {
case "added":
data.push(doc);
break;
case "modified":
const index = data.findIndex(item => item.id == doc.id);
data[index] = doc;
break;
case "removed":
data = data.filter(item => item.id !== doc.id);
break;
default:
break;
}
});
update(data);
});
기본적인 데이터 구조는 Javascript > React > Redux 로 3가지를 추가해보자
Firebase에 잘 저장된 것을 확인할 수 있다.
Data를 트리 계층구조로 변환시키기 위해 stratify를 사용한다.
d3.stratify는 d3-hierarchy API의 하나로,
평면 데이터 모델을 자신의 키와 부모의 키를 바탕으로 tree 구조의 데이터로 전환해준다.
const stratify = d3
.stratify()
.id(d => d.name)
.parentId(d => d.parent);
const rootNode = stratify(data);
console.log(rootNode);
콘솔을 확인해 보면 data가 계층구조가 된 것을 확인할 수 있다.
const tree = d3.tree().size([dims.width, dims.height]);
// update function
const update = data => {
const rootNode = stratify(data);
const treeData = tree(rootNode);
console.log(treeData);
};
tree() 에 사이즈를 적용해주고 Data를 적용해서 확인해 보면
x, y 값이 잘 적용됐고, 계층적 데이터 형식을 얻게 되었다!
이제 루트 노드를 통해 tree 생성기로 데이터를 전달해줘야 한다.
update 함수 안에 아래와 같이 작성해준다.
// get nodes selection and join data
const nodes = graph
.selectAll(".node") // .node = g
.data(treeData.descendants()); // 자손 연결
값을 반환하려면 각 데이터 객체의 x 및 y 좌표에 있는 데이터에 액세스해야 하기 때문에
그래서 D는 그 데이터로 전달된다.
그런 다음 출력하기 때문에 템플릿 문자열이 될 문자열을 반환할 겁니다.
// create enter node groups
const enterNodes = nodes
.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.x}, ${d.y})`);
// append rects to enter nodes
enterNodes
.append("rect")
.attr("fill", "#aaa")
.attr("stroke", "#555")
.attr("stroke-width", 2)
.attr("width", d => d.data.name.length * 20) // name의 길이만큼 width값 설정
.attr("height", 50);
지금 우리는 모든 노트를 문으로 출력하고 있고 각 노트는 직사각형과 텍스트를 포함하고 있다.
이제 데이터를 배열 형식으로 만드들어서 노드에 데이터를 결합해야한다.
// get link selection and join new data
const link = graph.selectAll('.link')
.data(tree(rootNode).links());
// enter new links
link.enter()
.append('path')
.transition().duration(300)
.attr('class', 'link')
.attr('fill', 'none')
.attr('stroke', '#aaa')
.attr('stroke-width', 2)
.attr('d', d3.linkVertical()
.x(d => d.x)
.y(d => d.y )
);
스타일 속성을 추가하고 이 데이터 안에서 본 x와 y의 위치를 지정해주면 연결된다.
각 데이터가 연결된 선이 그려졌다.
텍스트와 선이 왼쪽으로 치우쳐있기 때문에 위치를 바로잡아줘야한다.
enterNodes
.append("rect")
.attr("fill", "#aaa")
.attr("stroke", "#555")
.attr("stroke-width", 2)
.attr("width", d => d.data.name.length * 20) // name의 길이만큼 width값 설정
.attr("height", 50)
.attr("transform", d => { // 추가된 부분
let x = d.data.name.length * 20;
return `translate(${-x / 2}, -25)`; // text를 사각형 가운데 위치
});
새로운 데이터를 추가해보니 생각한 모양이 나오지 않는다.
가상 돔에 추가되는 것이기 때문에 지금 돔에는 아무것도 없을 것이고 그래서 나중에 우리가 데이터를 바꿀 때마다 돔의 모든 요소들과 모든 노드와 모든 요소들을 잡아내야한다.
// remove current nodes
graph.selectAll(".node").remove();
graph.selectAll(".link").remove();
update function 맨 위쪽에 이 두가지를 추가하면 해결된다.
각 부모 데이터 별로 컬러를 다르게 지정하려고 한다.
// color 지정
const color = d3.scaleOrdinal(["#27AE60", "#9B51E0", "#F2C94C", "#EB5757"]);
// *** update function 안쪽 *****
// update ordinal scale
color.domain(data.map(d => d.department));
enterNodes
.append("rect")
.attr("fill", d => color(d.data.department)) // **수정된 부분** color 적용
.attr("stroke", "#555")
.attr("stroke-width", 2)
.attr("width", d => d.data.name.length * 20) // name의 길이만큼 width값 설정
.attr("height", 50)
.attr("transform", d => {
let x = d.data.name.length * 20;
return `translate(${-x / 2}, -25)`; // text를 사각형 가운데 위치
});
이렇게 해서 차트를 완성했다!
예제 감사합니다 !