问题描述
我试图使用forceY()
属性以D3强制布局垂直排列我的节点,但是我没有限制y轴的上限。我的画布高度为200px,我要订购50个类别并将其投影到高度为1000px的平面上。我最初希望看到顶部的200px,而其余节点在底部看不到。如果需要,我让用户平移那里。
我将let y_scale = d3.scaleLinear().domain([0,50]).range([0,1000]);
与d3.forceSimulation(nodes).force("y",d3.forceY().y(d => y_scale(d.category)))
结合使用,我认为这样可以自然地以0 px及更低的像素绘制节点,但事实并非如此。节点是根据类别进行排序的,但是0类别并不会映射到0px,而是说-400px不在顶部。
有没有办法使forceY
真正映射到[0,1000]
范围?
我知道我可以使用y
之类的东西在svg上限制节点Math.max(0,d.y)
的情况,但这只会使所有从顶部拖尾的节点堆叠在0行。
解决方法
对于分类变量,应改为使用d3.scaleBand()
。那应该是造成奇怪定位的主要原因:
const fruits = ["Apple","Apple","Pear","Orange","Grape"];
const data = fruits.map(d => ({
x: 50,fruit: d
}));
const colours = ["Red","Green","Purple"];
const unique = arr => arr.filter((d,i) => arr.indexOf(d) === i);
const yscale = d3.scaleBand()
.domain(unique(fruits))
.range([50,150]);
const colour = d3.scaleOrdinal()
.domain(unique(fruits))
.range(colours);
const circle = d3.select("body")
.append("svg")
.attr("height",200)
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r",4)
.attr("cx",d => d.x)
.attr("cy",d => yscale(d.fruit))
.attr("fill",d => colour(d.fruit));
var graphLayout = d3.forceSimulation(data)
.force("collide",d3.forceCollide().radius(5))
.force("x",d3.forceX(50))
.force("y",d3.forceY().y(d => yscale(d.fruit)))
.on("tick",ticked);
function ticked() {
circle
.attr("cx",d => d.x)
.attr("cy",d => d.y)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>