
노드(node): 흐름의 출발점 또는 도착점이 되는 항목
링크(link): 두 노드를 연결하는 흐름 (흐름의 양을 value로 표현)
넓이(width): 링크의 넓이는 value 값에 비례
nodes: [
{ name: "A" },
{ name: "B" },
{ name: "C" }
]
따라서 노드 이름이 겹치면 어떤 노드를 가리키는지 알 수 없고 Sankey가 깨짐
links: [
{ source: 0, target: 1, value: 10 },
{ source: 1, target: 2, value: 5 }
]
source와 target은 nodes 배열의 인덱스 또는 d3.sankey().nodeId()로 정의한 식별자
value는 흐름의 양 (값이 클수록 링크가 두꺼워짐)
Sankey가 어색한 이유는, source와 target이 이어지는 흐름이 없기 때문
Case1 → Case4
Case4 → Case7
Case7 → Case10
단일 클러스터에 있는 노드들을 억지로 연결한 것이기 때문에 그래프가 직관적이지 않는 문제
클러스터 내에서 순서를 부여해서 1단계 → 2단계 → 3단계 식으로 만들고
연결을 해당 단계 간에만 하도록 바꿔야 함
const sankeyData = {
nodes: [
{ name: "서울" },
{ name: "부산" },
{ name: "대전" }
],
links: [
{ source: 0, target: 1, value: 5 },
{ source: 1, target: 2, value: 3 }
]
};
source: 0은 "서울",
target: 1은 "부산" → 5만큼 흐름이 있다는 의미
nodeId()로 문자열 기반 으로도 생성 가능
const sankey = d3.sankey()
.nodeId(d => d.name)
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 5]]);
const data = {
nodes: [
{ name: "서울" },
{ name: "부산" }
],
links: [
{ source: "서울", target: "부산", value: 10 }
]
};
이렇게 하면 index 없이도 name만으로도 Sankey를 만들 수 있음
{
nodes: [{ name: "A" }, { name: "B" }, { name: "C" }],
links: [{ source: 0, target: 1, value: 5 },
{ source: 1, target: 2, value: 3 }]
}
nodes는 고유한 노드만 있어야 함
links의 source와 target은 nodes 배열의 index를 가리킴
| 기능 | 설명 |
|---|---|
nodeId(d => d.name) | 문자열 기반 노드 연결 가능 |
.nodePadding(px) | 노드 간 간격 조절 |
.nodeWidth(px) | 노드 두께 조절 |
| .extent([[x0, y0], [x1, y1]]) | 그래프의 렌더링 범위 |
구글 chart - https://developers.google.com/chart/interactive/docs/gallery/sankey?hl=ko
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sankey Diagram Example</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://unpkg.com/d3-sankey@0.12.3/dist/d3-sankey.min.js"></script>
<style>
body { font-family: sans-serif; }
.node rect {
cursor: move;
fill-opacity: 0.9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
</style>
</head>
<body>
<h2>Sankey Diagram</h2>
<svg id="sankey" width="700" height="500"></svg>
<script>
const { sankey, sankeyLinkHorizontal } = d3;
// Sankey 노드 및 링크 데이터
const data = {
nodes: [
{ name: "A" },
{ name: "B" },
{ name: "C" },
{ name: "D" },
{ name: "E" }
],
links: [
{ source: 0, target: 1, value: 10 },
{ source: 0, target: 2, value: 5 },
{ source: 1, target: 3, value: 7 },
{ source: 2, target: 3, value: 3 },
{ source: 3, target: 4, value: 10 }
]
};
const svg = d3.select("#sankey");
const width = +svg.attr("width");
const height = +svg.attr("height");
const sankeyGen = sankey()
.nodeWidth(20)
.nodePadding(15)
.extent([[1, 1], [width - 1, height - 6]]);
const sankeyData = sankeyGen({
nodes: data.nodes.map(d => Object.assign({}, d)),
links: data.links.map(d => Object.assign({}, d))
});
// 링크 (곡선 흐름)
svg.append("g")
.selectAll("path")
.data(sankeyData.links)
.join("path")
.attr("d", sankeyLinkHorizontal())
.attr("stroke", "#69b3a2")
.attr("stroke-width", d => Math.max(1, d.width))
.attr("fill", "none")
.attr("opacity", 0.5);
// 노드 (박스)
const node = svg.append("g")
.selectAll("g")
.data(sankeyData.nodes)
.join("g")
.attr("transform", d => `translate(${d.x0},${d.y0})`);
node.append("rect")
.attr("height", d => d.y1 - d.y0)
.attr("width", d => d.x1 - d.x0)
.attr("fill", "#4682B4");
node.append("text")
.attr("x", -6)
.attr("y", d => (d.y1 - d.y0) / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "end")
.text(d => d.name);
</script>
</body>
</html>
1. Sankey 데이터 정의
const data = {
nodes: [
{ name: "A" },
{ name: "B" },
{ name: "C" },
{ name: "D" },
{ name: "E" }
],
links: [
{ source: 0, target: 1, value: 10 },
{ source: 0, target: 2, value: 5 },
{ source: 1, target: 3, value: 7 },
{ source: 2, target: 3, value: 3 },
{ source: 3, target: 4, value: 10 }
]
};
2. Snakey 생성
const sankeyGen = sankey()
.nodeWidth(20) // 노드 너비
.nodePadding(15) // 노드 간 간격
.extent([[1, 1], [width - 1, height - 6]]); // 그릴 범위
3. 실제 데이터 레이아웃 계산
const sankeyData = sankeyGen({
nodes: data.nodes.map(d => Object.assign({}, d)),
links: data.links.map(d => Object.assign({}, d))
});
4. Links(흐름 선) 그리기
svg.append("g")
.selectAll("path")
.data(sankeyData.links)
.join("path")
.attr("d", sankeyLinkHorizontal())
.attr("stroke", "#69b3a2")
.attr("stroke-width", d => Math.max(1, d.width))
.attr("fill", "none")
.attr("opacity", 0.5);
5. Node(박스) 그리기
const node = svg.append("g")
.selectAll("g")
.data(sankeyData.nodes)
.join("g")
.attr("transform", d => `translate(${d.x0},${d.y0})`);
6. 노드 사각형 박스 그리기
const node = svg.append("g")
.selectAll("g")
.data(sankeyData.nodes)
.join("g")
.attr("transform", d => `translate(${d.x0},${d.y0})`);