我创建了一个带有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轴显示数量。 每个点都是一个国家。
我想在不同年份的同一国家/地区之间添加连接线。我用这些颜色编码来澄清。
问题是,我不能让x / y与基于力的点匹配。我评论了我认为会起作用的东西。有什么想法吗?
答案 0 :(得分:3)
创建圆圈后,可以检索其精确坐标。使用这些圆坐标,可以很容易地设置线的末端:
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>
这样,线条末端与圆圈的坐标完全匹配。
为此,我们可以使用以下方法选择与相关行的末端相对应的圆圈:
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"));
附注:
注意我是如何修改用于命名圈子的类的。由于类名包含空格,我们无法选择它们;我用-
代替了。另外,我们显然无法选择以数字开头的类,所以我将类名改为以国家而不是数量开头。
因为在这里,类用于唯一地定义圆和线,所以设置id
而不是class
可能更有意义。
正如@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)...