D3 force layout forceY не сопоставляется с диапазоном

#javascript #d3.js #force-layout

#javascript #d3.js #принудительная компоновка

Вопрос:

Я пытаюсь упорядочить свои узлы по вертикали в D3 force layout, используя forceY() свойство, но мне не удается ограничить верхнюю границу моей оси y. Мой холст имеет высоту 200 пикселей, и у меня есть 50 категорий, которые я хочу упорядочить и спроецировать на плоскость высотой 1000 пикселей. Сначала я хочу видеть верхние 200 пикселей, в то время как остальные узлы могут скрываться из виду внизу. Я позволяю пользователю перемещаться туда, если это необходимо.

Я использовал 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 пикселей и ниже, но это не так. Узлы упорядочены на основе категорий, но категория 0 сопоставляется не с 0px, а с -400px, который находится вне поля зрения на верхней стороне.

Есть ли способ заставить forceY действительно сопоставить [0, 1000] диапазон?

Я знаю, что могу ограничить y количество узлов при рисовании в svg, используя что-то вроде Math.max(0, d.y) , но это приведет только к тому, что все узлы, которые будут располагаться сверху, будут уложены в строку 0.

Ответ №1:

Для категориальных переменных вы должны использовать d3.scaleBand() вместо этого. Это должно быть основной причиной странного позиционирования:

 const fruits = ["Apple", "Apple", "Pear", "Pear", "Orange", "Grape"];
const data = fruits.map(d => ({
  x: 50,
  fruit: d
}));
const colours = ["Red", "Green", "Orange", "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>