Beeswarm plot with force - 添加到节点的链接
Beeswarm plot with force - add links to nodes
我用 d3v4
和 d3.forceSimulation
创建了一个 Beeswarm 图,这些点是我希望它们所在的位置:
var data = [
{ country: "Algeria", amount: 22, year: 2000 },
{ country: "Argentina", amount: 49, year: 1990 },
{ country: "Armenia", amount: 3, year: 1990 },
{ country: "Australia", amount: 9, year: 2010 },
{ country: "Austria", amount: 1, year: 2010 },
{ country: "Bahamas", amount: 5, year: 2018 },
{ country: "Bahrain", amount: 22, year: 2018 },
{ country: "Belarus", amount: 9, year: 2010 },
{ country: "Belgium", amount: 46, year: 2018 },
{ country: "Brazil", amount: 79, year: 1990 },
{ country: "Canada", amount: 12, year: 2000 },
{ country: "China", amount: 26, year: 2018 },
{ country: "Colombia", amount: 9, year: 2010 },
{ country: "Croatia", amount: 8, year: 2000 },
{ country: "Cuba", amount: 14, year: 1990 },
{ country: "Czech Republic", amount: 11, year: 2018 },
{ country: "Denmark", amount: 125, year: 2010 },
{ country: "Canada", amount: 124, year: 2018 },
{ country: "Bahrain", amount: 39, year: 2010 },
{ country: "Estonia", amount: 141, year: 2018 },
{ country: "Ethiopia", amount: 38, year: 1990 },
{ country: "France", amount: 4, year: 2018 },
{ country: "Germany", amount: 15, year: 2000 },
{ country: "Greece", amount: 16, year: 2010 },
{ country: "Grenada", amount: 241, year: 2010 },
{ country: "Hungary", amount: 135, year: 1990 },
{ country: "India", amount: 22, year: 1990 },
{ country: "Indonesia", amount: 31, year: 1990 },
{ country: "Iran", amount: 88, year: 2010 },
{ country: "Ireland", amount: 12, year: 2018 },
{ country: "Italy", amount: 128, year: 2000 },
{ country: "Jamaica", amount: 1, year: 2018 },
{ country: "Japan", amount: 41, year: 1990 },
{ country: "Jordan", amount: 137, year: 2010 },
{ country: "Iran", amount: 13, year: 1990 },
{ country: "Malaysia", amount: 25, year: 2018 },
{ country: "Mexico", amount: 59, year: 2010 },
{ country: "Moldova", amount: 71, year: 2000 },
{ country: "Mongolia", amount: 22, year: 2018 },
{ country: "Morocco", amount: 131, year: 1990 },
{ country: "Netherlands", amount: 129, year: 2018 },
{ country: "New Zealand", amount: 148, year: 2018 },
{ country: "Niger", amount: 1, year: 2010 },
{ country: "Nigeria", amount: 41, year: 1990 },
{ country: "Norway", amount: 14, year: 2010 },
{ country: "Philippines", amount: 15, year: 2018 },
{ country: "Poland", amount: 12, year: 2010 },
{ country: "Portugal", amount: 31, year: 2000 },
{ country: "Puerto Rico", amount: 51, year: 2000 },
{ country: "Romania", amount: 15, year: 2000 },
{ country: "Serbia", amount: 18, year: 2000 },
{ country: "South Africa", amount: 14, year: 2010 },
{ country: "Sweden", amount: 11, year: 2018 },
{ country: "Switzerland", amount: 7, year: 2010 },
{ country: "Thailand", amount: 61, year: 2018 },
{ country: "Trinidad and Tobago", amount: 12, year: 2018 },
{ country: "Tunisia", amount: 34, year: 2010 },
{ country: "Turkey", amount: 28, year: 2010 },
{ country: "Ukraine", amount: 11, year: 2010 },
{ country: "Uzbekistan", amount: 123, year: 2018 },
{ country: "Venezuela", amount: 23, year: 2018 },
{ country: "Iran", amount: 13, year: 2018 }
];
var width = 1000,
height = 500;
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
var x = d3.scaleLinear()
.range([95, 650]);
var y = d3.scaleLinear()
.range([100, 450]);
data.forEach(d => {
d.amount = +d.amount;
});
var sort = data.sort((a, b) => d3.descending(a, b));
y.domain(d3.extent(data, function(d) {
return d.amount;
}));
x.domain(d3.extent(data, function(d) {
return d.year;
}));
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) {
return x(d.year);
}).strength(3))
.force("y", d3.forceY(function(d) {
return y(d.amount)
}).strength(2))
.force("collide", d3.forceCollide(7).strength(7))
.stop();
for (var i = 0; i < data.length * 2; ++i) simulation.tick();
var circles = svg.selectAll(".circles")
.data(data);
var circlesEnter = circles.enter()
.append("circle");
circlesEnter.attr("r", 4)
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
.attr("fill", function(d) {
if (d.country == "Iran") {
return "#FF0044"
} else if (d.country == "Canada") {
return "#00A9E9"
} else if (d.country == "Bahrain") {
return "#6BF4C6"
} else {
return '#333'
}
})
.attr('class', function(d) {
return d.amount + ' ' + d.year + ' ' + d.country
})
// connector lines
var byCountry = d3.nest()
.key(function(d) {
return d.country;
})
.entries(data);
var countryNames = d3.values(byCountry).map(function(d) {
return d.values.map(function(v) {
return v.country;
}).join(', ');
});
for (i = 0; i < countryNames.length; i++) {
eaco = countryNames[i].split(',')[0]
const filterByCountry = (country, data) => item => item.country === country
connectData = data.filter(filterByCountry(eaco))
var linesGroup = svg.append("g")
.attr("class", "connectors");
var linec = d3.line()
.x(function(d) {
return x(d.year)
})
.y(function(d) {
return y(d.amount)
})
// using below as the points does not work
// .x(function(d) { return x(d.x)})
// .y(function(d) { return y(d.y)})
var lineGraph = linesGroup.selectAll('.connect')
.data(connectData)
.enter()
.append("path")
.attr('class', function(d) {
return d.amount + ' ' + d.year + ' ' + d.country
})
.attr("d", linec(connectData))
.attr("stroke", function(d) {
if (d.country == "Iran") {
return "#FF0044"
} else if (d.country == "Canada") {
return "#00A9E9"
} else if (d.country == "Bahrain") {
return "#6BF4C6"
} else {
return '#333'
}
})
.attr("stroke-width", 1)
.attr("fill", "none")
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<body><div id="chart"></div></body>
x 轴显示年份。
y 轴显示数量。
每个点代表一个国家。
我想在不同年份的同一国家/地区的点之间添加连接线。我 color-coded 这些要澄清。
问题是,我无法让 x/y 与基于力的点相匹配。我评论了我认为可行的内容。有什么想法吗?
一旦创建了圆圈,就可以检索它们的确切坐标。使用这些圆坐标,设置线的末端变得非常容易:
var data = [
{ country: "Algeria", amount: 22, year: 2000 },
{ country: "Argentina", amount: 49, year: 1990 },
{ country: "Armenia", amount: 3, year: 1990 },
{ country: "Australia", amount: 9, year: 2010 },
{ country: "Austria", amount: 1, year: 2010 },
{ country: "Bahamas", amount: 5, year: 2018 },
{ country: "Bahrain", amount: 22, year: 2018 },
{ country: "Belarus", amount: 9, year: 2010 },
{ country: "Belgium", amount: 46, year: 2018 },
{ country: "Brazil", amount: 79, year: 1990 },
{ country: "Canada", amount: 12, year: 2000 },
{ country: "China", amount: 26, year: 2018 },
{ country: "Colombia", amount: 9, year: 2010 },
{ country: "Croatia", amount: 8, year: 2000 },
{ country: "Cuba", amount: 14, year: 1990 },
{ country: "Czech Republic", amount: 11, year: 2018 },
{ country: "Denmark", amount: 125, year: 2010 },
{ country: "Canada", amount: 124, year: 2018 },
{ country: "Bahrain", amount: 39, year: 2010 },
{ country: "Estonia", amount: 141, year: 2018 },
{ country: "Ethiopia", amount: 38, year: 1990 },
{ country: "France", amount: 4, year: 2018 },
{ country: "Germany", amount: 15, year: 2000 },
{ country: "Greece", amount: 16, year: 2010 },
{ country: "Grenada", amount: 241, year: 2010 },
{ country: "Hungary", amount: 135, year: 1990 },
{ country: "India", amount: 22, year: 1990 },
{ country: "Indonesia", amount: 31, year: 1990 },
{ country: "Iran", amount: 88, year: 2010 },
{ country: "Ireland", amount: 12, year: 2018 },
{ country: "Italy", amount: 128, year: 2000 },
{ country: "Jamaica", amount: 1, year: 2018 },
{ country: "Japan", amount: 41, year: 1990 },
{ country: "Jordan", amount: 137, year: 2010 },
{ country: "Iran", amount: 13, year: 1990 },
{ country: "Malaysia", amount: 25, year: 2018 },
{ country: "Mexico", amount: 59, year: 2010 },
{ country: "Moldova", amount: 71, year: 2000 },
{ country: "Mongolia", amount: 22, year: 2018 },
{ country: "Morocco", amount: 131, year: 1990 },
{ country: "Netherlands", amount: 129, year: 2018 },
{ country: "New Zealand", amount: 148, year: 2018 },
{ country: "Niger", amount: 1, year: 2010 },
{ country: "Nigeria", amount: 41, year: 1990 },
{ country: "Norway", amount: 14, year: 2010 },
{ country: "Philippines", amount: 15, year: 2018 },
{ country: "Poland", amount: 12, year: 2010 },
{ country: "Portugal", amount: 31, year: 2000 },
{ country: "Puerto Rico", amount: 51, year: 2000 },
{ country: "Romania", amount: 15, year: 2000 },
{ country: "Serbia", amount: 18, year: 2000 },
{ country: "South Africa", amount: 14, year: 2010 },
{ country: "Sweden", amount: 11, year: 2018 },
{ country: "Switzerland", amount: 7, year: 2010 },
{ country: "Thailand", amount: 61, year: 2018 },
{ country: "Trinidad and Tobago", amount: 12, year: 2018 },
{ country: "Tunisia", amount: 34, year: 2010 },
{ country: "Turkey", amount: 28, year: 2010 },
{ country: "Ukraine", amount: 11, year: 2010 },
{ country: "Uzbekistan", amount: 123, year: 2018 },
{ country: "Venezuela", amount: 23, year: 2018 },
{ country: "Iran", amount: 13, year: 2018 }
];
var width = 1000,
height = 500;
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
var x = d3.scaleLinear()
.range([95, 650]);
var y = d3.scaleLinear()
.range([100, 450]);
data.forEach(d => {
d.amount = +d.amount;
});
var sort = data.sort((a, b) => d3.descending(a, b));
y.domain(d3.extent(data, function(d) {
return d.amount;
}));
x.domain(d3.extent(data, function(d) {
return d.year;
}));
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) {
return x(d.year);
}).strength(3))
.force("y", d3.forceY(function(d) {
return y(d.amount)
}).strength(2))
.force("collide", d3.forceCollide(7).strength(7))
.stop();
for (var i = 0; i < data.length * 2; ++i) simulation.tick();
var circles = svg.selectAll(".circles").data(data);
var circlesEnter = circles.enter().append("circle");
circlesEnter.attr("r", 4)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("fill", function(d) {
if (d.country == "Iran") {
return "#FF0044"
} else if (d.country == "Canada") {
return "#00A9E9"
} else if (d.country == "Bahrain") {
return "#6BF4C6"
} else {
return '#333'
}
})
.attr('class', function(d) {
return d.country + '-' + d.year + '-' + d.amount
});
// connector lines
var byCountry = d3.nest().key(function(d) { return d.country; }).entries(data);
var countryNames = d3.values(byCountry).map(function(d) {
return d.values.map(function(v) {
return v.country;
}).join(', ');
});
for (i = 0; i < countryNames.length; i++) {
eaco = countryNames[i].split(',')[0]
const filterByCountry = (country, data) => item => item.country === country
connectData = data.filter(filterByCountry(eaco))
if (connectData.length >= 2) {
var linesGroup = svg.append("g")
.attr("class", "connectors");
var linec = d3.line()
.x(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx"))
.y(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cy"));
linesGroup
.datum(connectData)
.append("path")
.attr('class', d => d[0].amount + '-' + d[0].year + '-' + d[0].country)
.attr("d", linec)
.attr("stroke", function(d) {
if (d[0].country == "Iran") return "#FF0044";
else if (d[0].country == "Canada") return "#00A9E9";
else if (d[0].country == "Bahrain") return "#6BF4C6";
else return '#333';
})
.attr("stroke-width", 1)
.attr("fill", "none");
}
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<body><div id="chart"></div></body>
这样,线的末端与圆的坐标完全匹配。
为此,我们可以 select 圆圈对应于所讨论的线的末端,使用:
d3.select("circle." + d.country + "-" + d.year + "-" + d.amount)
我们可以在其中检索 cx
和 cy
属性(圆的 x 和 y 位置):
d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx")
最终创建具有这些坐标的线:
var linec = d3.line()
.x(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx"))
.y(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cy"));
旁注:
请注意我是如何修改您用来命名圈子的 classes 的。由于 class 名称包含空格,我们不能 select 它们;我改用 -
。此外,我们显然不能 select 以数字开头的 class,所以我将 class 名称更改为以国家而不是数量开头。
由于此处 classes 用于唯一定义圆和线,因此设置 id
s 而不是 class
es 可能更有意义。
正如@Gerardo 所注意到的,每条线实际上被创建了几次(每个圆一次;这样连接 3 个圆的线将被创建 3 次)。这是您可以解决该问题的一种可能方法:
var linec = d3.line()
.x(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx"))
.y(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cy"));
linesGroup.datum(connectData).append("path").attr("d", linec)...
我用 d3v4
和 d3.forceSimulation
创建了一个 Beeswarm 图,这些点是我希望它们所在的位置:
var data = [
{ country: "Algeria", amount: 22, year: 2000 },
{ country: "Argentina", amount: 49, year: 1990 },
{ country: "Armenia", amount: 3, year: 1990 },
{ country: "Australia", amount: 9, year: 2010 },
{ country: "Austria", amount: 1, year: 2010 },
{ country: "Bahamas", amount: 5, year: 2018 },
{ country: "Bahrain", amount: 22, year: 2018 },
{ country: "Belarus", amount: 9, year: 2010 },
{ country: "Belgium", amount: 46, year: 2018 },
{ country: "Brazil", amount: 79, year: 1990 },
{ country: "Canada", amount: 12, year: 2000 },
{ country: "China", amount: 26, year: 2018 },
{ country: "Colombia", amount: 9, year: 2010 },
{ country: "Croatia", amount: 8, year: 2000 },
{ country: "Cuba", amount: 14, year: 1990 },
{ country: "Czech Republic", amount: 11, year: 2018 },
{ country: "Denmark", amount: 125, year: 2010 },
{ country: "Canada", amount: 124, year: 2018 },
{ country: "Bahrain", amount: 39, year: 2010 },
{ country: "Estonia", amount: 141, year: 2018 },
{ country: "Ethiopia", amount: 38, year: 1990 },
{ country: "France", amount: 4, year: 2018 },
{ country: "Germany", amount: 15, year: 2000 },
{ country: "Greece", amount: 16, year: 2010 },
{ country: "Grenada", amount: 241, year: 2010 },
{ country: "Hungary", amount: 135, year: 1990 },
{ country: "India", amount: 22, year: 1990 },
{ country: "Indonesia", amount: 31, year: 1990 },
{ country: "Iran", amount: 88, year: 2010 },
{ country: "Ireland", amount: 12, year: 2018 },
{ country: "Italy", amount: 128, year: 2000 },
{ country: "Jamaica", amount: 1, year: 2018 },
{ country: "Japan", amount: 41, year: 1990 },
{ country: "Jordan", amount: 137, year: 2010 },
{ country: "Iran", amount: 13, year: 1990 },
{ country: "Malaysia", amount: 25, year: 2018 },
{ country: "Mexico", amount: 59, year: 2010 },
{ country: "Moldova", amount: 71, year: 2000 },
{ country: "Mongolia", amount: 22, year: 2018 },
{ country: "Morocco", amount: 131, year: 1990 },
{ country: "Netherlands", amount: 129, year: 2018 },
{ country: "New Zealand", amount: 148, year: 2018 },
{ country: "Niger", amount: 1, year: 2010 },
{ country: "Nigeria", amount: 41, year: 1990 },
{ country: "Norway", amount: 14, year: 2010 },
{ country: "Philippines", amount: 15, year: 2018 },
{ country: "Poland", amount: 12, year: 2010 },
{ country: "Portugal", amount: 31, year: 2000 },
{ country: "Puerto Rico", amount: 51, year: 2000 },
{ country: "Romania", amount: 15, year: 2000 },
{ country: "Serbia", amount: 18, year: 2000 },
{ country: "South Africa", amount: 14, year: 2010 },
{ country: "Sweden", amount: 11, year: 2018 },
{ country: "Switzerland", amount: 7, year: 2010 },
{ country: "Thailand", amount: 61, year: 2018 },
{ country: "Trinidad and Tobago", amount: 12, year: 2018 },
{ country: "Tunisia", amount: 34, year: 2010 },
{ country: "Turkey", amount: 28, year: 2010 },
{ country: "Ukraine", amount: 11, year: 2010 },
{ country: "Uzbekistan", amount: 123, year: 2018 },
{ country: "Venezuela", amount: 23, year: 2018 },
{ country: "Iran", amount: 13, year: 2018 }
];
var width = 1000,
height = 500;
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
var x = d3.scaleLinear()
.range([95, 650]);
var y = d3.scaleLinear()
.range([100, 450]);
data.forEach(d => {
d.amount = +d.amount;
});
var sort = data.sort((a, b) => d3.descending(a, b));
y.domain(d3.extent(data, function(d) {
return d.amount;
}));
x.domain(d3.extent(data, function(d) {
return d.year;
}));
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) {
return x(d.year);
}).strength(3))
.force("y", d3.forceY(function(d) {
return y(d.amount)
}).strength(2))
.force("collide", d3.forceCollide(7).strength(7))
.stop();
for (var i = 0; i < data.length * 2; ++i) simulation.tick();
var circles = svg.selectAll(".circles")
.data(data);
var circlesEnter = circles.enter()
.append("circle");
circlesEnter.attr("r", 4)
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
.attr("fill", function(d) {
if (d.country == "Iran") {
return "#FF0044"
} else if (d.country == "Canada") {
return "#00A9E9"
} else if (d.country == "Bahrain") {
return "#6BF4C6"
} else {
return '#333'
}
})
.attr('class', function(d) {
return d.amount + ' ' + d.year + ' ' + d.country
})
// connector lines
var byCountry = d3.nest()
.key(function(d) {
return d.country;
})
.entries(data);
var countryNames = d3.values(byCountry).map(function(d) {
return d.values.map(function(v) {
return v.country;
}).join(', ');
});
for (i = 0; i < countryNames.length; i++) {
eaco = countryNames[i].split(',')[0]
const filterByCountry = (country, data) => item => item.country === country
connectData = data.filter(filterByCountry(eaco))
var linesGroup = svg.append("g")
.attr("class", "connectors");
var linec = d3.line()
.x(function(d) {
return x(d.year)
})
.y(function(d) {
return y(d.amount)
})
// using below as the points does not work
// .x(function(d) { return x(d.x)})
// .y(function(d) { return y(d.y)})
var lineGraph = linesGroup.selectAll('.connect')
.data(connectData)
.enter()
.append("path")
.attr('class', function(d) {
return d.amount + ' ' + d.year + ' ' + d.country
})
.attr("d", linec(connectData))
.attr("stroke", function(d) {
if (d.country == "Iran") {
return "#FF0044"
} else if (d.country == "Canada") {
return "#00A9E9"
} else if (d.country == "Bahrain") {
return "#6BF4C6"
} else {
return '#333'
}
})
.attr("stroke-width", 1)
.attr("fill", "none")
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<body><div id="chart"></div></body>
x 轴显示年份。 y 轴显示数量。 每个点代表一个国家。
我想在不同年份的同一国家/地区的点之间添加连接线。我 color-coded 这些要澄清。
问题是,我无法让 x/y 与基于力的点相匹配。我评论了我认为可行的内容。有什么想法吗?
一旦创建了圆圈,就可以检索它们的确切坐标。使用这些圆坐标,设置线的末端变得非常容易:
var data = [
{ country: "Algeria", amount: 22, year: 2000 },
{ country: "Argentina", amount: 49, year: 1990 },
{ country: "Armenia", amount: 3, year: 1990 },
{ country: "Australia", amount: 9, year: 2010 },
{ country: "Austria", amount: 1, year: 2010 },
{ country: "Bahamas", amount: 5, year: 2018 },
{ country: "Bahrain", amount: 22, year: 2018 },
{ country: "Belarus", amount: 9, year: 2010 },
{ country: "Belgium", amount: 46, year: 2018 },
{ country: "Brazil", amount: 79, year: 1990 },
{ country: "Canada", amount: 12, year: 2000 },
{ country: "China", amount: 26, year: 2018 },
{ country: "Colombia", amount: 9, year: 2010 },
{ country: "Croatia", amount: 8, year: 2000 },
{ country: "Cuba", amount: 14, year: 1990 },
{ country: "Czech Republic", amount: 11, year: 2018 },
{ country: "Denmark", amount: 125, year: 2010 },
{ country: "Canada", amount: 124, year: 2018 },
{ country: "Bahrain", amount: 39, year: 2010 },
{ country: "Estonia", amount: 141, year: 2018 },
{ country: "Ethiopia", amount: 38, year: 1990 },
{ country: "France", amount: 4, year: 2018 },
{ country: "Germany", amount: 15, year: 2000 },
{ country: "Greece", amount: 16, year: 2010 },
{ country: "Grenada", amount: 241, year: 2010 },
{ country: "Hungary", amount: 135, year: 1990 },
{ country: "India", amount: 22, year: 1990 },
{ country: "Indonesia", amount: 31, year: 1990 },
{ country: "Iran", amount: 88, year: 2010 },
{ country: "Ireland", amount: 12, year: 2018 },
{ country: "Italy", amount: 128, year: 2000 },
{ country: "Jamaica", amount: 1, year: 2018 },
{ country: "Japan", amount: 41, year: 1990 },
{ country: "Jordan", amount: 137, year: 2010 },
{ country: "Iran", amount: 13, year: 1990 },
{ country: "Malaysia", amount: 25, year: 2018 },
{ country: "Mexico", amount: 59, year: 2010 },
{ country: "Moldova", amount: 71, year: 2000 },
{ country: "Mongolia", amount: 22, year: 2018 },
{ country: "Morocco", amount: 131, year: 1990 },
{ country: "Netherlands", amount: 129, year: 2018 },
{ country: "New Zealand", amount: 148, year: 2018 },
{ country: "Niger", amount: 1, year: 2010 },
{ country: "Nigeria", amount: 41, year: 1990 },
{ country: "Norway", amount: 14, year: 2010 },
{ country: "Philippines", amount: 15, year: 2018 },
{ country: "Poland", amount: 12, year: 2010 },
{ country: "Portugal", amount: 31, year: 2000 },
{ country: "Puerto Rico", amount: 51, year: 2000 },
{ country: "Romania", amount: 15, year: 2000 },
{ country: "Serbia", amount: 18, year: 2000 },
{ country: "South Africa", amount: 14, year: 2010 },
{ country: "Sweden", amount: 11, year: 2018 },
{ country: "Switzerland", amount: 7, year: 2010 },
{ country: "Thailand", amount: 61, year: 2018 },
{ country: "Trinidad and Tobago", amount: 12, year: 2018 },
{ country: "Tunisia", amount: 34, year: 2010 },
{ country: "Turkey", amount: 28, year: 2010 },
{ country: "Ukraine", amount: 11, year: 2010 },
{ country: "Uzbekistan", amount: 123, year: 2018 },
{ country: "Venezuela", amount: 23, year: 2018 },
{ country: "Iran", amount: 13, year: 2018 }
];
var width = 1000,
height = 500;
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height);
var x = d3.scaleLinear()
.range([95, 650]);
var y = d3.scaleLinear()
.range([100, 450]);
data.forEach(d => {
d.amount = +d.amount;
});
var sort = data.sort((a, b) => d3.descending(a, b));
y.domain(d3.extent(data, function(d) {
return d.amount;
}));
x.domain(d3.extent(data, function(d) {
return d.year;
}));
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) {
return x(d.year);
}).strength(3))
.force("y", d3.forceY(function(d) {
return y(d.amount)
}).strength(2))
.force("collide", d3.forceCollide(7).strength(7))
.stop();
for (var i = 0; i < data.length * 2; ++i) simulation.tick();
var circles = svg.selectAll(".circles").data(data);
var circlesEnter = circles.enter().append("circle");
circlesEnter.attr("r", 4)
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("fill", function(d) {
if (d.country == "Iran") {
return "#FF0044"
} else if (d.country == "Canada") {
return "#00A9E9"
} else if (d.country == "Bahrain") {
return "#6BF4C6"
} else {
return '#333'
}
})
.attr('class', function(d) {
return d.country + '-' + d.year + '-' + d.amount
});
// connector lines
var byCountry = d3.nest().key(function(d) { return d.country; }).entries(data);
var countryNames = d3.values(byCountry).map(function(d) {
return d.values.map(function(v) {
return v.country;
}).join(', ');
});
for (i = 0; i < countryNames.length; i++) {
eaco = countryNames[i].split(',')[0]
const filterByCountry = (country, data) => item => item.country === country
connectData = data.filter(filterByCountry(eaco))
if (connectData.length >= 2) {
var linesGroup = svg.append("g")
.attr("class", "connectors");
var linec = d3.line()
.x(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx"))
.y(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cy"));
linesGroup
.datum(connectData)
.append("path")
.attr('class', d => d[0].amount + '-' + d[0].year + '-' + d[0].country)
.attr("d", linec)
.attr("stroke", function(d) {
if (d[0].country == "Iran") return "#FF0044";
else if (d[0].country == "Canada") return "#00A9E9";
else if (d[0].country == "Bahrain") return "#6BF4C6";
else return '#333';
})
.attr("stroke-width", 1)
.attr("fill", "none");
}
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<body><div id="chart"></div></body>
这样,线的末端与圆的坐标完全匹配。
为此,我们可以 select 圆圈对应于所讨论的线的末端,使用:
d3.select("circle." + d.country + "-" + d.year + "-" + d.amount)
我们可以在其中检索 cx
和 cy
属性(圆的 x 和 y 位置):
d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx")
最终创建具有这些坐标的线:
var linec = d3.line()
.x(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx"))
.y(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cy"));
旁注:
请注意我是如何修改您用来命名圈子的 classes 的。由于 class 名称包含空格,我们不能 select 它们;我改用 -
。此外,我们显然不能 select 以数字开头的 class,所以我将 class 名称更改为以国家而不是数量开头。
由于此处 classes 用于唯一定义圆和线,因此设置 id
s 而不是 class
es 可能更有意义。
正如@Gerardo 所注意到的,每条线实际上被创建了几次(每个圆一次;这样连接 3 个圆的线将被创建 3 次)。这是您可以解决该问题的一种可能方法:
var linec = d3.line()
.x(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cx"))
.y(d => d3.select("circle." + d.country + "-" + d.year + "-" + d.amount).attr("cy"));
linesGroup.datum(connectData).append("path").attr("d", linec)...