Я использую d3, чтобы составить линейную диаграмму, которая должна поддерживать до 100 пунктов, делая ее очень переполненной. Проблема в том, что некоторые ярлыки перекрываются.
Метод, который я пытался задействовать, рисовал все точки, затем отдельно рисовал все ярлыки и запускал силовое столкновение на ярлыках, чтобы остановить их перекрытие, а затем после столкновения силы вычерчивать линию между каждой из меток и связанной с ними точкой.
Я не могу заставить силы работать, не говоря уже о чертежах.
Любые предложения для лучшего способа сделать это также приветствуются.
Вот мой код:
$.each(data.responseJSON.responsedata, function(k, v) {
var thispoint = svg.append("g").attr("transform", "translate("+pointx+","+pointy+")");
thispoint.append("circle").attr("r", 10).style("fill","darkBlue").style("stroke","black");
var label = svg.append("text").text(v.conceptName).style("text-anchor", "end").attr("font-family", "Calibri");
label.attr("transform", "translate("+(pointx)+","+(pointy-12)+") rotate(90)")
});
nodes = d3.selectAll("text")
simulation = d3.forceSimulation(nodes)
.force("x", d3.forceX().strength(10))
.force("y", d3.forceY().strength(10))
.force("collide",d3.forceCollide(20).strength(5))
.velocityDecay(0.15);
ticks = 0;
simulation.nodes(data)
.on("tick", d => {
ticks = ticks + 1;
d3.select(this).attr("x", function(d) { return d.x }).attr("y", function(d) { return d.x });
console.log("updated" + this)
});
Силовая компоновка - относительно дорогой способ перемещения меток во избежание столкновения. Это итеративно и вычислительно интенсивно.
Более эффективные алгоритмы добавляют метки по одному, определяя наилучшее положение для каждого. Например, "жадная" стратегия добавляет каждую метку последовательно, выбирая позицию, где метка имеет самое низкое совпадение с уже добавленными метками.
Я создал компоненты D3, d3fc-label-layout, который реализует несколько стратегий компоновки меток:
https://github.com/d3fc/d3fc-label-layout
Вот пример того, как его использовать:
// Use the text label component for each datapoint. This component renders both
// a text label and a circle at the data-point origin. For this reason, we don't
// need to use a scatter / point series.
const labelPadding = 2;
const textLabel = fc.layoutTextLabel()
.padding(2)
.value(d => d.language);
// a strategy that combines simulated annealing with removal
// of overlapping labels
const strategy = fc.layoutRemoveOverlaps(fc.layoutGreedy());
// create the layout that positions the labels
const labels = fc.layoutLabel(strategy)
.size((d, i, g) => {
// measure the label and add the required padding
const textSize = g[i].getElementsByTagName('text')[0].getBBox();
return [
textSize.width,
textSize.height
];
})
.position((d) => {
return [
d.users,
d.orgs
]
})
.component(textLabel);
https://bl.ocks.org/ColinEberhardt/27508a7c0832d6e8132a9d1d8aaf231c
"tick"
слушателе не используйте функцию стрелки, если вы хотите, чтобыthis
был элемент DOM. Измените его на обычную функцию.