D3力布局forceY未映射到范围

问题描述

我试图使用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>