如何更新 d3-force 元素?
How to update d3-force elements?
我想在点击时更新 d3-force
个元素,但我发现了一些错误。
我想要的是:
- 首先生成所有 links
- 然后生成所有节点,让节点可以覆盖link
所以当点击元素时应该是:
<svg>
<line ...></line>
<line ...></line>
<line ...></line>
<line ...></line>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
</svg>
但我得到:
<svg>
<g><circle ...></circle></g>
<line ...></line>
<line ...></line>
<line ...></line>
<line ...></line>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
</svg>
const width = 800;
const height = 400;
const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]);
let node = svg.selectAll(".node");
let link = svg.selectAll(".link");
const simulation = d3
.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", (d) => {
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);
node
.attr("transform", d => `translate(${d.x}, ${d.y})`);
});
const root = {
x: width / 2,
y: height / 2,
level: 0,
id: "1"
};
function genChildren(p) {
const dist = 100 / (p.level + 1);
const nodes = [{
x: p.x - dist,
y: p.y,
level: p.level + 1,
id: p.id + "1"
},
{
x: p.x + dist,
y: p.y,
level: p.level + 1,
id: p.id + "2"
},
{
x: p.x,
y: p.y - dist,
level: p.level + 1,
id: p.id + "3"
},
{
x: p.x,
y: p.y + dist,
level: p.level + 1,
id: p.id + "4"
},
];
const links = nodes.map(v => ({
source: p.id,
target: v.id
}));
return {
nodes,
links
};
}
function update(nodes, links) {
link = link.data(links)
.join(enter => {
return enter.append("line")
.attr("stroke", "#000")
.attr("stroke-width", "1.5px")
.call(enter => enter.transition().attr("stroke-opacity", 1));
}, update => update, exit => exit.remove());
node = node.data(nodes, d => d.id)
.join(
enter => {
const g = enter.append("g");
g.append("circle")
.attr("r", 12)
.attr("cursor", "move")
.attr("fill", "#ccc")
.attr("stroke", "#000")
.attr("stroke-width", "1.5px");
return g;
},
update => update,
exit => exit.remove(),
)
.attr("transform", function(d) {
return "translate(" + d.x + ", " + d.y + ")";
})
.on("click", (ev, d) => {
if (d.level !== 0) {
return;
}
const childrenInfo = genChildren(root)
const nodes = d.active ? [root] : [root].concat(childrenInfo.nodes);
const links = d.active ? [] : childrenInfo.links;
d.active = !d.active;
update(nodes, links);
});
simulation.nodes(nodes).force("link", d3.forceLink(links).id(node => node.id).strength(-0.01));
if (simulation.alpha() <= 1) {
simulation.alpha(1);
simulation.restart();
}
}
update([root], [])
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>
您可以为节点和链接设置单独的 g
元素,将链接添加到第一个,将节点添加到第二个。或者,您可以只使用 node.raise()
将这些元素放在 DOM:
中的所有链接之后
const width = 800;
const height = 400;
const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]);
let node = svg.selectAll(".node");
let link = svg.selectAll(".link");
const simulation = d3
.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", (d) => {
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);
node
.attr("transform", d => `translate(${d.x}, ${d.y})`);
});
const root = {
x: width / 2,
y: height / 2,
level: 0,
id: "1"
};
function genChildren(p) {
const dist = 100 / (p.level + 1);
const nodes = [{
x: p.x - dist,
y: p.y,
level: p.level + 1,
id: p.id + "1"
},
{
x: p.x + dist,
y: p.y,
level: p.level + 1,
id: p.id + "2"
},
{
x: p.x,
y: p.y - dist,
level: p.level + 1,
id: p.id + "3"
},
{
x: p.x,
y: p.y + dist,
level: p.level + 1,
id: p.id + "4"
},
];
const links = nodes.map(v => ({
source: p.id,
target: v.id
}));
return {
nodes,
links
};
}
function update(nodes, links) {
link = link.data(links)
.join(enter => {
return enter.append("line")
.attr("stroke", "#000")
.attr("stroke-width", "1.5px")
.call(enter => enter.transition().attr("stroke-opacity", 1));
}, update => update, exit => exit.remove());
node = node.data(nodes, d => d.id)
.join(
enter => {
const g = enter.append("g");
g.append("circle")
.attr("r", 12)
.attr("cursor", "move")
.attr("fill", "#ccc")
.attr("stroke", "#000")
.attr("stroke-width", "1.5px");
return g;
},
update => update,
exit => exit.remove(),
)
.raise()
.attr("transform", function(d) {
return "translate(" + d.x + ", " + d.y + ")";
})
.on("click", (ev, d) => {
if (d.level !== 0) {
return;
}
const childrenInfo = genChildren(root)
const nodes = d.active ? [root] : [root].concat(childrenInfo.nodes);
const links = d.active ? [] : childrenInfo.links;
d.active = !d.active;
update(nodes, links);
});
simulation.nodes(nodes).force("link", d3.forceLink(links).id(node => node.id).strength(-0.01));
if (simulation.alpha() <= 1) {
simulation.alpha(1);
simulation.restart();
}
}
update([root], [])
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>
我想在点击时更新 d3-force
个元素,但我发现了一些错误。
我想要的是:
- 首先生成所有 links
- 然后生成所有节点,让节点可以覆盖link
所以当点击元素时应该是:
<svg>
<line ...></line>
<line ...></line>
<line ...></line>
<line ...></line>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
</svg>
但我得到:
<svg>
<g><circle ...></circle></g>
<line ...></line>
<line ...></line>
<line ...></line>
<line ...></line>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
<g><circle ...></circle></g>
</svg>
const width = 800;
const height = 400;
const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]);
let node = svg.selectAll(".node");
let link = svg.selectAll(".link");
const simulation = d3
.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", (d) => {
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);
node
.attr("transform", d => `translate(${d.x}, ${d.y})`);
});
const root = {
x: width / 2,
y: height / 2,
level: 0,
id: "1"
};
function genChildren(p) {
const dist = 100 / (p.level + 1);
const nodes = [{
x: p.x - dist,
y: p.y,
level: p.level + 1,
id: p.id + "1"
},
{
x: p.x + dist,
y: p.y,
level: p.level + 1,
id: p.id + "2"
},
{
x: p.x,
y: p.y - dist,
level: p.level + 1,
id: p.id + "3"
},
{
x: p.x,
y: p.y + dist,
level: p.level + 1,
id: p.id + "4"
},
];
const links = nodes.map(v => ({
source: p.id,
target: v.id
}));
return {
nodes,
links
};
}
function update(nodes, links) {
link = link.data(links)
.join(enter => {
return enter.append("line")
.attr("stroke", "#000")
.attr("stroke-width", "1.5px")
.call(enter => enter.transition().attr("stroke-opacity", 1));
}, update => update, exit => exit.remove());
node = node.data(nodes, d => d.id)
.join(
enter => {
const g = enter.append("g");
g.append("circle")
.attr("r", 12)
.attr("cursor", "move")
.attr("fill", "#ccc")
.attr("stroke", "#000")
.attr("stroke-width", "1.5px");
return g;
},
update => update,
exit => exit.remove(),
)
.attr("transform", function(d) {
return "translate(" + d.x + ", " + d.y + ")";
})
.on("click", (ev, d) => {
if (d.level !== 0) {
return;
}
const childrenInfo = genChildren(root)
const nodes = d.active ? [root] : [root].concat(childrenInfo.nodes);
const links = d.active ? [] : childrenInfo.links;
d.active = !d.active;
update(nodes, links);
});
simulation.nodes(nodes).force("link", d3.forceLink(links).id(node => node.id).strength(-0.01));
if (simulation.alpha() <= 1) {
simulation.alpha(1);
simulation.restart();
}
}
update([root], [])
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>
您可以为节点和链接设置单独的 g
元素,将链接添加到第一个,将节点添加到第二个。或者,您可以只使用 node.raise()
将这些元素放在 DOM:
const width = 800;
const height = 400;
const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]);
let node = svg.selectAll(".node");
let link = svg.selectAll(".link");
const simulation = d3
.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", (d) => {
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);
node
.attr("transform", d => `translate(${d.x}, ${d.y})`);
});
const root = {
x: width / 2,
y: height / 2,
level: 0,
id: "1"
};
function genChildren(p) {
const dist = 100 / (p.level + 1);
const nodes = [{
x: p.x - dist,
y: p.y,
level: p.level + 1,
id: p.id + "1"
},
{
x: p.x + dist,
y: p.y,
level: p.level + 1,
id: p.id + "2"
},
{
x: p.x,
y: p.y - dist,
level: p.level + 1,
id: p.id + "3"
},
{
x: p.x,
y: p.y + dist,
level: p.level + 1,
id: p.id + "4"
},
];
const links = nodes.map(v => ({
source: p.id,
target: v.id
}));
return {
nodes,
links
};
}
function update(nodes, links) {
link = link.data(links)
.join(enter => {
return enter.append("line")
.attr("stroke", "#000")
.attr("stroke-width", "1.5px")
.call(enter => enter.transition().attr("stroke-opacity", 1));
}, update => update, exit => exit.remove());
node = node.data(nodes, d => d.id)
.join(
enter => {
const g = enter.append("g");
g.append("circle")
.attr("r", 12)
.attr("cursor", "move")
.attr("fill", "#ccc")
.attr("stroke", "#000")
.attr("stroke-width", "1.5px");
return g;
},
update => update,
exit => exit.remove(),
)
.raise()
.attr("transform", function(d) {
return "translate(" + d.x + ", " + d.y + ")";
})
.on("click", (ev, d) => {
if (d.level !== 0) {
return;
}
const childrenInfo = genChildren(root)
const nodes = d.active ? [root] : [root].concat(childrenInfo.nodes);
const links = d.active ? [] : childrenInfo.links;
d.active = !d.active;
update(nodes, links);
});
simulation.nodes(nodes).force("link", d3.forceLink(links).id(node => node.id).strength(-0.01));
if (simulation.alpha() <= 1) {
simulation.alpha(1);
simulation.restart();
}
}
update([root], [])
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>