结合 D3 饼图和分层边缘捆绑
Combine D3 Pie Chart and Hierarchical Edge Bundling
如何使用 D3 实现此目的? desired output
两层饼图很简单https://embed.plnkr.co/plunk/2p0zmp
或者使用带有图形和节点的 d3 网络,http://using-d3js.com/05_08_links.html
但我如何将“节点”和“链接”的概念叠加到饼图的这些弧线上?
首选哪种数据结构?
{
nodes: [
{
layer: 1,
data: [
{name: A },
{name: B },
{name: C },
{name: D }
]
},
{
layer: 2,
data: [
{name: E },
{name: F },
{name: G }
]
}
],
links: [{ source: 'B', target: 'E'}, { source: 'D', target: 'F'}]
}
这与您要查找的内容非常接近。您可以使用一些额外的圆弧生成器和 arc.centroid() to get the positions for the start and ends of the links. Then you can use a link generator 来绘制链接。这样做的一个缺点是链接可能会与节点重叠。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
/*
set up
*/
const width = 700;
const height = 400;
const svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height);
const g = svg.append('g')
.attr('transform', `translate(${width / 2},${height})`);
/*
data
*/
const level1Nodes = [
{ name: 'A', value: 50, level: 1, color: 'RoyalBlue' },
{ name: 'B', value: 50, level: 1, color: 'DarkOrange' },
{ name: 'C', value: 50, level: 1, color: 'DarkOrange' },
{ name: 'D', value: 30, level: 1, color: 'Gold' }
];
const level2Nodes = [
{ name: 'E', value: 75, level: 2, color: 'RoyalBlue' },
{ name: 'F', value: 75, level: 2, color: 'DarkOrange' },
{ name: 'G', value: 30, level: 2, color: 'RoyalBlue' },
];
const links = [
{ source: 'B', target: 'E'},
{ source: 'D', target: 'F'}
];
/*
pie generator
*/
const pie = d3.pie()
.value(d => d.value)
.startAngle(-Math.PI / 2)
.endAngle(Math.PI / 2)
.padAngle(Math.PI / 45);
// calculate the angles for the slices of the nodes
const slices = [
...pie(level1Nodes),
...pie(level2Nodes)
];
/*
arcs
*/
const level1InnerRadius = 130;
const level1OuterRadius = 200;
const level2InnerRadius = 270;
const level2OuterRadius = 340;
// for drawing the nodes
const level1Arc = d3.arc()
.innerRadius(level1InnerRadius)
.outerRadius(level1OuterRadius);
const level2Arc = d3.arc()
.innerRadius(level2InnerRadius)
.outerRadius(level2OuterRadius);
const levelToArc = new Map([
[1, level1Arc],
[2, level2Arc]
]);
// for positioning the links along the outside
// of the level 1 nodes and the inside of the
// level 2 nodes
const level1OuterArc = d3.arc()
.innerRadius(level1OuterRadius)
.outerRadius(level1OuterRadius);
const level2InnerArc = d3.arc()
.innerRadius(level2InnerRadius)
.outerRadius(level2InnerRadius);
/*
calculating position of links
*/
// Map from the name of a node to the data for its arc
const nameToSlice = d3.index(slices, d => d.data.name);
// get the start and end positions for each link
const linkPositions = links.map(({source, target}) => ({
source: level1OuterArc.centroid(nameToSlice.get(source)),
target: level2InnerArc.centroid(nameToSlice.get(target)),
}));
/*
drawing
*/
// nodes
g.append('g')
.selectAll('path')
.data(slices)
.join('path')
.attr('d', d => levelToArc.get(d.data.level)(d))
.attr('fill', d => d.data.color);
// node labels
const labelsGroup = g.append('g')
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bold')
.attr('font-size', 30)
.selectAll('text')
.data(slices)
.join('text')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('transform', d => `translate(${levelToArc.get(d.data.level).centroid(d)})`)
.text(d => d.data.name);
// links
g.append('g')
.selectAll('path')
.data(linkPositions)
.join('path')
.attr('d', d3.linkVertical())
.attr('fill', 'none')
.attr('stroke', 'DarkBlue')
.attr('stroke-width', 2);
// circles at the end of links
g.append('g')
.selectAll('circle')
.data(linkPositions.map(({source, target}) => [source, target]).flat())
.join('circle')
.attr('r', 5)
.attr('fill', 'DarkBlue')
.attr('transform', d => `translate(${d})`);
</script>
</body>
</html>
如何使用 D3 实现此目的? desired output
两层饼图很简单https://embed.plnkr.co/plunk/2p0zmp
或者使用带有图形和节点的 d3 网络,http://using-d3js.com/05_08_links.html
但我如何将“节点”和“链接”的概念叠加到饼图的这些弧线上?
首选哪种数据结构?
{
nodes: [
{
layer: 1,
data: [
{name: A },
{name: B },
{name: C },
{name: D }
]
},
{
layer: 2,
data: [
{name: E },
{name: F },
{name: G }
]
}
],
links: [{ source: 'B', target: 'E'}, { source: 'D', target: 'F'}]
}
这与您要查找的内容非常接近。您可以使用一些额外的圆弧生成器和 arc.centroid() to get the positions for the start and ends of the links. Then you can use a link generator 来绘制链接。这样做的一个缺点是链接可能会与节点重叠。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
/*
set up
*/
const width = 700;
const height = 400;
const svg = d3.select('#chart')
.append('svg')
.attr('width', width)
.attr('height', height);
const g = svg.append('g')
.attr('transform', `translate(${width / 2},${height})`);
/*
data
*/
const level1Nodes = [
{ name: 'A', value: 50, level: 1, color: 'RoyalBlue' },
{ name: 'B', value: 50, level: 1, color: 'DarkOrange' },
{ name: 'C', value: 50, level: 1, color: 'DarkOrange' },
{ name: 'D', value: 30, level: 1, color: 'Gold' }
];
const level2Nodes = [
{ name: 'E', value: 75, level: 2, color: 'RoyalBlue' },
{ name: 'F', value: 75, level: 2, color: 'DarkOrange' },
{ name: 'G', value: 30, level: 2, color: 'RoyalBlue' },
];
const links = [
{ source: 'B', target: 'E'},
{ source: 'D', target: 'F'}
];
/*
pie generator
*/
const pie = d3.pie()
.value(d => d.value)
.startAngle(-Math.PI / 2)
.endAngle(Math.PI / 2)
.padAngle(Math.PI / 45);
// calculate the angles for the slices of the nodes
const slices = [
...pie(level1Nodes),
...pie(level2Nodes)
];
/*
arcs
*/
const level1InnerRadius = 130;
const level1OuterRadius = 200;
const level2InnerRadius = 270;
const level2OuterRadius = 340;
// for drawing the nodes
const level1Arc = d3.arc()
.innerRadius(level1InnerRadius)
.outerRadius(level1OuterRadius);
const level2Arc = d3.arc()
.innerRadius(level2InnerRadius)
.outerRadius(level2OuterRadius);
const levelToArc = new Map([
[1, level1Arc],
[2, level2Arc]
]);
// for positioning the links along the outside
// of the level 1 nodes and the inside of the
// level 2 nodes
const level1OuterArc = d3.arc()
.innerRadius(level1OuterRadius)
.outerRadius(level1OuterRadius);
const level2InnerArc = d3.arc()
.innerRadius(level2InnerRadius)
.outerRadius(level2InnerRadius);
/*
calculating position of links
*/
// Map from the name of a node to the data for its arc
const nameToSlice = d3.index(slices, d => d.data.name);
// get the start and end positions for each link
const linkPositions = links.map(({source, target}) => ({
source: level1OuterArc.centroid(nameToSlice.get(source)),
target: level2InnerArc.centroid(nameToSlice.get(target)),
}));
/*
drawing
*/
// nodes
g.append('g')
.selectAll('path')
.data(slices)
.join('path')
.attr('d', d => levelToArc.get(d.data.level)(d))
.attr('fill', d => d.data.color);
// node labels
const labelsGroup = g.append('g')
.attr('font-family', 'sans-serif')
.attr('font-weight', 'bold')
.attr('font-size', 30)
.selectAll('text')
.data(slices)
.join('text')
.attr('dominant-baseline', 'middle')
.attr('text-anchor', 'middle')
.attr('transform', d => `translate(${levelToArc.get(d.data.level).centroid(d)})`)
.text(d => d.data.name);
// links
g.append('g')
.selectAll('path')
.data(linkPositions)
.join('path')
.attr('d', d3.linkVertical())
.attr('fill', 'none')
.attr('stroke', 'DarkBlue')
.attr('stroke-width', 2);
// circles at the end of links
g.append('g')
.selectAll('circle')
.data(linkPositions.map(({source, target}) => [source, target]).flat())
.join('circle')
.attr('r', 5)
.attr('fill', 'DarkBlue')
.attr('transform', d => `translate(${d})`);
</script>
</body>
</html>