#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>