gpt4 book ai didi

javascript - ForceX 使用 DOM 元素作为坐标引用

转载 作者:行者123 更新时间:2023-12-04 02:26:41 26 4
gpt4 key购买 nike

自 D3v4 起,可以定义 .forceCenter.forceX.forceY,而不是 .size在 D3v3 中。

var force = d3.forceSimulation()
.force("center", d3.forceCenter(width/2, heigth/2))

var force = d3.forceSimulation()
.force("x", d3.forceX(500))
.force("y", d3.forceY(500))

所以可以设置坐标,那么也一定可以使用另一个 DOM 元素的 x 和 y 值作为引用。一个正确的用例是直观地组合 d3 力图。 问题是如何使用 DOM 元素作为引用,因为获取这些 DOM element.x 和 element.y 值似乎非常科学。

  • .getBBox() 返回错误的坐标,
  • d3.transform 已被 API 删除,
  • .attr("x") 返回 null

目标是从outerNode A获取x和y位置,并将这些值用于.force("x")和.force("y”)。这些值肯定可以在自己的函数的帮助下在刻度函数中更新,但现在我正在努力获取这些坐标。

 var innerLayout = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(50))
.force("charge", d3.forceManyBody().strength(-50))
.force("x", d3.forceX(870)) //## <--- DOM element.x reference
.force("y", d3.forceY(370)) //## <--- DOM element.y reference
.force("collision", d3.forceCollide().radius(6))

enter image description here

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>D3v6 Pack</title>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>

<style>
body {
background-color: #e6e7ee;
}

circle {
fill: whitesmoke;
stroke: black;
stroke-width: 1px;
}

</style>

<body>
<script>
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight)
.attr("class", "svg")
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")

var outerLinkContainer = svg.append("g").attr("class", "outerLinkContainer")
var outerNodeContainer = svg.append("g").attr("class", "outerNodeContainer")

var innerLinkContainer = svg.append("g").attr("class", "innerLinkContainer")
var innerNodeContainer = svg.append("g").attr("class", "innerNodeContainer")

////////////////////////
// outer force layout

var outerData = {
"nodes": [
{ "id": "A" },
{ "id": "B" },
],
"links": [
{ "source": "B", "target": "A" },
]
}

var outerLayout = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
.force("charge", d3.forceManyBody().strength(-650))
.force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))
.force("collision", d3.forceCollide().radius(50))

var outerLinks = outerLinkContainer.selectAll(".link")
.data(outerData.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("opacity", 0.2)

var outerNodes = outerNodeContainer.selectAll("g.outer")
.data(outerData.nodes, function (d) { return d.id; })
.enter()
.append("g")
.attr("class", "outer")
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)

outerNodes
.append("circle")
.attr("r", 40)

outerNodes.selectAll("text")
.data(d => [d])
.join("text")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("id", function(d) { return "text" + d.id })
.text(function (d) {
return d.id
})

outerLayout
.nodes(outerData.nodes)
.on("tick", outerTick)

outerLayout
.force("link")
.links(outerData.links)

////////////////////////
// inner force layouts


var innerAdata = {
"nodes": [
{ "id": "B1" },
{ "id": "B2" },
{ "id": "B3" },
],
"links": [
{ "source": "B1", "target": "B2" },
{ "source": "B2", "target": "B3" },
{ "source": "B3", "target": "B1" }
]
}


var innerLayout = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(50))
.force("charge", d3.forceManyBody().strength(-50))
.force("x", d3.forceX(250)) //## <--- DOM element.x reference
.force("y", d3.forceY(250)) //## <--- DOM element.y reference
.force("collision", d3.forceCollide().radius(6))

var innerLinks = innerLinkContainer.selectAll(".link")
.data(innerAdata.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("opacity", 0.5)

var innerNodes = innerNodeContainer.selectAll("g.inner")
.data(innerAdata.nodes, function (d) { return d.id; })
.enter()
.append("g")
.attr("class", "inner")
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)

innerNodes
.append("circle")
.style("fill", "orange")
.style("stroke", "blue")
.attr("r", 6);

innerLayout
.nodes(innerAdata.nodes)
.on("tick", innerAtick)

innerLayout
.force("link")
.links(innerAdata.links)

function outerTick() {
outerLinks
.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});

outerNodes.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}

function innerAtick() {
innerLinks
.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});

innerNodes.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}


function dragStarted(event, d) {
if (!event.active)

outerLayout.alphaTarget(0.3).restart();
innerLayout.alphaTarget(0.3).restart();

d.fx = d.x;
d.fy = d.y;
}

function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}

function dragEnded(event, d) {
if (!event.active)

outerLayout.alphaTarget(0);
innerLayout.alphaTarget(0);

d.fx = undefined;
d.fy = undefined;
}

</script>
</body>

</html>

最佳答案

下面代码片段中的方法特定于您的示例(并且与之前的 question 相关)并依赖于以下内容:

首先,用外部节点初始化力模拟...

... 意味着您始终可以引用数据对象来获取 svg 内的外部节点坐标。所以对于g包含outerNodes outerData包含xy您正在寻找的outerLayout之后已计算出这些位置,例如:

{
"id": "A",
"index": 0,
"x": 409.28298494419124,
"y": 321.93152757995455,
"vy": -0.0005382622043197348,
"vx": 0.0006924019130575043
}

请注意,这通常不起作用,因为对于嵌套组,xy与其父组相关。外部节点坐标相对于 svg所以它们就是您要查找的坐标。

其次,要解决内力不受外力约束的问题...

...使用 d3.forceRadial 。工作示例有点罕见,但您可以深入了解 this one作者:@GerardoFurtado,我发现它对于提供这个答案很有用。还有这个block还有这个answer .

对于内在力量,我使用以下代码:

const innerForce = d3.forceSimulation(subgraphNodes)
.force("r", d3.forceRadial(outerRadius - outerMargin).strength(1.1))
.force("charge", d3.forceCollide().radius(innerRadius))
.force("link", d3.forceLink().id(d => d.id).distance(outerRadius - outerMargin));

地点:

  • subgraphNodes就像你的innerData (但我已经组合了示例中的数据对象)
  • outerRadius是外部节点的半径,outerMargin是一些填充,用于将内部节点保留在外部节点内部。 1.1 看起来相当合理 strength参数以将内部节点限制在其外部节点内(但请注意,您仍然可以将它们拖到外部)
  • 使用innerRadius对于 d3.forceCollide
  • 使用相同的 outerRadius - outerMargin对于distanced3.forceLink

因此,虽然您可以将内部节点拖出外部节点,但它会弹回到外部节点内半径上的位置(由 outerRadius - outerMargin 定义)。这似乎对外部节点的布局影响很小,这是有帮助的。

第三,我正在为每个外部节点设置一个内部力布局......

(至少在我使用的示例数据中,这是它的工作原理):

graph.outer.nodes.forEach(outerNode => {
const parent = svg.select(`g.outer#${outerNode.id}`)
const subgraphNodes = graph[`inner${outerNode.id}`].nodes;
const subgraphLinks = graph[`inner${outerNode.id}`].links;

const innerLinks = parent.selectAll("g.inner")
.data(subgraphLinks)
.join("line")
.attr("class", "link")
.style("stroke", "black");

const innerNodes = parent.selectAll("g.inner")
// ...

});

通过将内部节点嵌套在其父节点(外部节点)中,您可以获得 Gerardo 在他的答案中提到的结果:

the inner force in a group already translated by the outer one

对于创建的每个内部布局,我都会在数组中跟踪它们,以便我们可以引用 nodes , links , parentforce稍后在 ticked和各种drag功能:

childSets.push({
force: innerForce,
parent: outerNode.id,
nodes: innerNodes,
links: innerLinks
});

第四,在ticked功能:

function ticked() {
outerLinks
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);

outerNodes.attr("transform", d => `translate(${d.x},${d.y})`);

childSets.forEach(set => {

const parent = graph.outer.nodes.find(n => n.id === set.parent);

set.nodes.attr("transform", d => `translate(${parent.x + d.x},${parent.y + d.y})`);

set.links
.attr("x1", d => d.source.x + parent.x)
.attr("y1", d => d.source.y + parent.y)
.attr("x2", d => d.target.x + parent.x)
.attr("y2", d => d.target.y + parent.y);

});
}

治疗outerNodesouterLinks与您的代码相同。

对于内部力布局,我迭代为每个 forceSimulation 创建的数组初始化并设置transformtranslate 所在的节点上指的是内部节点坐标和父节点坐标 - 父节点坐标来自:

const parent = graph.outer.nodes.find(n => n.id === set.parent);

然后我们可以引用parent.xparent.y我们需要的地方。

五、更新drag处理程序,以便处理每个内部布局

例如

function dragStarted(event, d) {
if (!event.active) {
outerForce.alphaTarget(0.3).restart();
childSets.forEach(set => set.force.alphaTarget(0.3).restart());
}
d.fx = d.x;
d.fy = d.y;
}

最后:把它们放在一起:

const width = 600;
const height = 400;
const outerRadius = 40;
const outerMargin = 16;
const innerRadius = 6;
const outerLinkDistance = 100;
let childSets = [];

const graph = {
outer: {
nodes: [
{id: "A"},
{id: "B"},
{id: "C"},
{id: "D"},
{id: "E"},
{id: "F"},
{id: "G"},
],
links: [
{source: "A", target: "B"},
{source: "B", target: "E"},
{source: "C", target: "F"},
{source: "C", target: "A"},
{source: "E", target: "A"},
{source: "D", target: "C"},
{source: "D", target: "F"},
{source: "A", target: "F"},
{source: "B", target: "G"},
{source: "G", target: "C"},
]
},
innerA: {
nodes: [
{id: "A1", parent: "A"},
{id: "A2", parent: "A"},
{id: "A3", parent: "A"},
],
links: [
{source: "A1", target: "A2"},
{source: "A2", target: "A3"},
{source: "A3", target: "A1"},
]
},
innerB: {
nodes: [
{id: "B1", parent: "B"},
{id: "B2", parent: "B"},
{id: "B3", parent: "B"},
{id: "B4", parent: "B"},
{id: "B5", parent: "B"},
],
links: [
{source: "B1", target: "B2"},
{source: "B2", target: "B3"},
{source: "B3", target: "B4"},
{source: "B4", target: "B5"},
{source: "B5", target: "B1"},
{source: "B1", target: "B3"},
{source: "B3", target: "B5"},
]
},
innerC: {
nodes: [
{id: "C1", parent: "C"},
{id: "C2", parent: "C"},
],
links: [
{source: "C1", target: "C2"}
]
},
innerD: {
nodes: [
{id: "D1", parent: "D"},
{id: "D2", parent: "D"},
],
links: []
},
innerE: {
nodes: [
{id: "E1", parent: "E"},
{id: "E2", parent: "E"},
{id: "E3", parent: "E"},
],
links: [
{source: "E1", target: "E2"},
{source: "E2", target: "E3"},
{source: "E3", target: "E1"},
]
},
innerF: {
nodes: [
{id: "F1", parent: "F"},
{id: "F2", parent: "F"},
{id: "F3", parent: "F"},
{id: "F4", parent: "F"},
],
links: [
{source: "F1", target: "F2"},
{source: "F1", target: "F3"},
{source: "F3", target: "F4"},
]
},
innerG: {
nodes: [
{id: "G1", parent: "G"}
],
links: []
}
}

const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);

const outerLinkG = svg.append("g")
.attr("class", "outerlinks");

const outerNodeG = svg.append("g")
.attr("class", "outernodes");

const outerForce = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(d => d.id).distance(outerLinkDistance));

const outerLinks = outerLinkG.selectAll(".link")
.data(graph.outer.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("opacity", 0.2);

const outerNodes = outerNodeG.selectAll("g.outer")
.data(graph.outer.nodes, d => d.id)
.join("g")
.attr("class", "outer")
.attr("id", d => d.id)
.append("circle")
.style("fill", "pink")
.style("stroke", "blue")
.attr("r", outerRadius)
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
);

outerForce
.nodes(graph.outer.nodes)
.on("tick", ticked);

outerForce
.force("link")
.links(graph.outer.links);

graph.outer.nodes.forEach(outerNode => {
const parent = svg.select(`g.outer#${outerNode.id}`)
const subgraphNodes = graph[`inner${outerNode.id}`].nodes;
const subgraphLinks = graph[`inner${outerNode.id}`].links;

const innerLinks = parent.selectAll("g.inner")
.data(subgraphLinks)
.join("line")
.attr("class", "link")
.style("stroke", "black");

const innerNodes = parent.selectAll("g.inner")
.data(subgraphNodes, d=> d.id)
.join("g")
.attr("class", "inner")
.attr("id", d => d.id)
.append("circle")
.attr("r", innerRadius)
.style("fill", "orange")
.style("stroke", "blue")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
);

// https://gerardofurtado.com/vr/vr.html
const innerForce = d3.forceSimulation(subgraphNodes)
.force("r", d3.forceRadial(outerRadius - outerMargin).strength(1.1))
.force("charge", d3.forceCollide().radius(innerRadius))
.force("link", d3.forceLink().id(d => d.id).distance(outerRadius - outerMargin));

innerForce
.on("tick", ticked);

innerForce
.force("link")
.links(subgraphLinks);

childSets.push({
force: innerForce,
parent: outerNode.id,
nodes: innerNodes,
links: innerLinks
});

});

function ticked() {
outerLinks
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);

outerNodes.attr("transform", d => `translate(${d.x},${d.y})`);

childSets.forEach(set => {

const parent = graph.outer.nodes.find(n => n.id === set.parent);

set.nodes.attr("transform", d => `translate(${parent.x + d.x},${parent.y + d.y})`);

set.links
.attr("x1", d => d.source.x + parent.x)
.attr("y1", d => d.source.y + parent.y)
.attr("x2", d => d.target.x + parent.x)
.attr("y2", d => d.target.y + parent.y);

});
}

function dragStarted(event, d) {
if (!event.active) {
outerForce.alphaTarget(0.3).restart();
childSets.forEach(set => set.force.alphaTarget(0.3).restart());
}
d.fx = d.x;
d.fy = d.y;
}

function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}

function dragEnded(event, d) {
if (!event.active) {
outerForce.alphaTarget(0);
childSets.forEach(set => set.force.alphaTarget(0));
}
d.fx = undefined;
d.fy = undefined;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js"></script>

这种方法似乎不可行的是在具有不同外部节点的内部节点之间创建链接。关于外部节点内内部节点的“ block 状”性质,还有一个需要改进的地方。

关于javascript - ForceX 使用 DOM 元素作为坐标引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67125421/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com