#javascript #svg #d3.js #d3-force-directed
#javascript #svg #d3.js #d3-принудительно направленный
Вопрос:
Я новичок d3-force
, я нахожу, что трудно изменить угол наклона линий и узлов, вы можете запустить мой код здесь.
В любом случае, мой код очень прост:
const width = 800;
const height = 400;
const centerX = width / 2;
const centerY = height / 2;
const graph = ({
nodes: Array.from({length:8}, () => ({})),
links: [
{source: 1, target: 0},
{source: 2, target: 0},
{source: 3, target: 0},
{source: 4, target: 0},
{source: 5, target: 0},
{source: 6, target: 0},
{source: 7, target: 0},
]
});
const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]),
link = svg
.selectAll(".link")
.data(graph.links)
.join("line"),
node = svg
.selectAll(".node")
.data(graph.nodes)
.join("g");
node.append("circle")
.attr("r", 12)
.attr("cursor", "move")
.attr("fill", "#ccc")
.attr("stroke", "#000")
.attr("stroke-width", "1.5px");
node.append("text").attr("dy", 25).text(function(d) {return d.index})
const simulation = d3
.forceSimulation()
.nodes(graph.nodes)
.force("link", d3.forceLink(graph.links).distance(100))
.force("charge", d3.forceManyBody().strength(-400))
.force("center", d3.forceCenter(width / 2, height / 2))
.stop();
for (let i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; i) {
simulation.tick();
}
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y)
.attr("stroke", "#000")
.attr("stroke-width", "1.5px")
node
.attr("transform", function (d) {return "translate(" d.x ", " d.y ")";});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>
Он генерирует svg следующим образом:
Я копаю источник d3-force
here, обнаруживаю, что он добавляет Math.PI * (3 - Math.sqrt(5))
каждый раз.
Вот что я хочу:
- что такое угол? —> равномерно распределяется по всем узлам, поэтому, если у нас есть
n
узлы, это будетMath.PI / (n-1)
- как насчет порядка? —> сохранить порядок по часовой стрелке
- где находится первый узел? —> начинается с
Math.PI / (n-1) / 2
- а как насчет дочерних элементов дочерних элементов? —> следуйте тому же правилу, но на половину длины родительской ссылки.
Комментарии:
1. Но разве они не будут перекрывать родительские элементы?
2. @RubenHelsloot О, я забыл сказать, что ссылка должна быть короче родительской
Ответ №1:
Я бы не стал использовать d3-force, а просто рассчитал позиции самостоятельно, используя некоторую базовую тригонометрию:
const width = 600;
const height = 400;
const centerX = width / 2;
const centerY = height / 2;
const graph = ({
nodes: d3.range(8).map(i => ({ id: i })),
links: [{
source: 0,
target: 1
},
{
source: 0,
target: 2
},
{
source: 0,
target: 3
},
{
source: 0,
target: 4
},
{
source: 1,
target: 5
},
{
source: 1,
target: 6
},
{
source: 1,
target: 7
},
]
});
graph.root = graph.nodes[0];
graph.nodes.forEach(n => {
n.children = [];
});
// Replace ID's with references to the nodes themselves
graph.links.forEach(l => {
l.source = graph.nodes.find(n => n.id === l.source);
l.target = graph.nodes.find(n => n.id === l.target);
// Register the target as a child of the source
l.source.children.push(l.target);
l.target.parent = l.source;
});
// Place the nodes
graph.nodes.forEach(n => {
if(n.parent === undefined) {
// root
n.x = centerX;
n.y = centerY;
n.level = 0;
return;
}
const parent = n.parent;
n.level = parent.level 1;
const nSiblings = parent.children.length;
const ithSibling = parent.children.indexOf(n);
// Position the node
const angle = 2 * Math.PI / nSiblings; // in radians
const startAngle = - angle / 2;
const radius = 200 - 60 * n.level;
console.log(angle, startAngle);
n.x = parent.x radius * Math.cos((ithSibling 1) * angle startAngle);
// Use a plus to keep the order clockwise, - for counterclockwise
n.y = parent.y radius * Math.sin((ithSibling 1) * angle startAngle);
});
const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]),
link = svg
.selectAll(".link")
.data(graph.links)
.join("line"),
node = svg
.selectAll(".node")
.data(graph.nodes)
.join("g");
node.append("circle")
.attr("r", 12)
.attr("cursor", "move")
.attr("fill", "#ccc")
.attr("stroke", "#000")
.attr("stroke-width", "1.5px");
node.append("text").attr("dy", 25).text(function(d) {
return d.id
});
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y)
.attr("stroke", "#000")
.attr("stroke-width", "1.5px")
node
.attr("transform", function(d) {
return "translate(" d.x ", " d.y ")";
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>