gpt4 book ai didi

javascript - D3 调整linkText位置

转载 作者:行者123 更新时间:2023-12-05 03:18:10 26 4
gpt4 key购买 nike

我的强制图有两个问题。 第一个 问题是指链接文本的位置。我添加了 50% 的偏移量,以确保每个 linkText 都居中。如果 linkText 不长,这会很好用。但是完全看起来

描述越长就越尴尬。

我不确定是否可以计算所需 linkText 空间的长度并以某种方式从给定的 offSet 中减去它。一般来说,所需的空间量就在那里,不知道是否可以用于进一步的计算。

enter image description here

我的第二个 问题与链接曲线有关。我添加了那些能够可视化双向链接。否则,它们将相互叠加。问题是,一旦您使用目标节点并以某种方式拖动它们,目标节点的 X 位置小于源节点的 X 位置,linkText 就会错误地弯曲。

enter image description here

也许你们有想法或提示。

       console.log("D3 Forced Layout ready.")

////////////////////////////////////////////////////////////
//////////////////// D3 Forced Graph ///////////////////////
////////////////////////////////////////////////////////////

var data = {
"nodes": [
{ "id": 1 },
{ "id": 2 },
{ "id": 3 },
{ "id": 4 },
{ "id": 5 }
],
"links": [
{ "source": 1, "target": 2, "text": "this description is not centered"},
{ "source": 2, "target": 1, "text": "Shorter description" },
{ "source": 2, "target": 3, "text": "Shorter description" },
{ "source": 3, "target": 4, "text": "even shorter" },
{ "source": 4, "target": 5, "text": "shorter" },
{ "source": 5, "target": 1, "text": "short" }
]
}

initForceLayout()

function initForceLayout() {
let vw = 800
let vh = 800

const svg = d3.select("#chart").append("svg")
.attr("width", vw)
.attr("height", vh)

const forceLayout = svg.append("g")
.attr("id", "forceLayout")
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.on("dblclick.zoom", null)

linksContainer = forceLayout.append("g").attr("class", "linkscontainer")
nodesContainer = forceLayout.append("g").attr("class", "nodesContainer")

var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id; }).distance(300))
.force('charge', d3.forceManyBody().strength(-400))
.force('center', d3.forceCenter(vw / 2, vh / 2))

link = linksContainer.selectAll("g")
.data(data.links)
.join("g")
.attr("cursor", "pointer")

linkLine = linksContainer.selectAll(".linkPath")
.data(data.links)
.join("path")
.attr("id", function(_,i) {
return "path" + i
})
.attr("stroke", "black")
.attr("opacity", 0.75)
.attr("stroke-width", 3)
.attr("fill", "transparent")

linkText = linksContainer.selectAll(".linkLabel")
.data(data.links)
.join("text")
.attr("dy", -10)
.attr("class", "linkLabel")
.attr("id", function (d, i) {return "linkLabel" + i })
.text("")

linkText.append("textPath")
.attr("xlink:href", function (_, i) {
return "#path" + i
})
.attr("startOffset", "50%")
.attr("opacity", 0.75)
.attr("cursor", "pointer")
.attr("class", "linkText")
.text(function (d) {
return d.text
})

node = nodesContainer.selectAll(".node")
.data(data.nodes, d => d.id)
.join("g")
.attr("class", "node")
.call(d3.drag()
.on("start", function(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
})
.on("drag", function(event, d) {
d.fx = event.x;
d.fy = event.y;
})
.on("end", function(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
})
)

node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 30)
.attr("fill", "whitesmoke")
.attr("stroke", "white")
.attr("stroke-width", 2)

simulation
.nodes(data.nodes)
.on("tick", function () {
// update link positions
linkLine.attr("d", function (d) {
if (d.target.x > d.source.x) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy)
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
} else if (d.target.x < d.source.x) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy)
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
});

// update node positions
node
.attr("transform", function (d) {
return "translate(" + d.x + ", " + d.y + ")";
});

linkText.attr("transform", function (d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();

rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
} else if (d.target.x > d.source.x) {
var bbox = this.getBBox();

rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(0 ' + rx + ' ' + ry + ')';
}
})
})

simulation
.force("link")
.links(data.links)

}
    :root {
--bs-gradient-dark-right: #141727;
--bs-gradient-dark-left: #3a416f;
}

html, body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}

body {
background-color: lightgray;
overflow: hidden;
}

.border-radius-lg {
border-radius: 0.75rem;
}

.svg-container {
display: inline-block;
position: relative;
width: 100%;
padding-bottom: 100%; /* aspect ratio */
vertical-align: top;
overflow: hidden;
}

#svg-content-responsive {
display: inline-block;
position: absolute;
top: 10px;
left: 0;
}

svg .rect {
fill: gold;
stroke: steelblue;
stroke-width: 5px;
}
<!DOCTYPE html>
<html>

<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>linkText</title>
<meta name="viewport" content="width=device-width, initial-scale=1">

<!-- D3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js" charset="utf-8"></script>
</head>

<body>
<div id="chart"></div>
</body>
</html>

最佳答案

解决方案

  1. 将文本居中对齐:添加了 text-anchor:middle属性(property)<text />足以使文本居中。

    虽然这不是必需的,但我还在 textPath 中添加了文本长度计算。元素作为数据属性。有了这个,手动对齐文本也是可能的。
textLink./* ... */.each(function(d,i) {
// ANSWER EDIT: precalculate text width
var thisWidth = this.getComputedTextLength()
d3.select(this).attr('data-text-width', () => thisWidth)
})
  1. 使用 textPath 固定 180 度旋转:正确交换 <textPath /> 的 Y 轴文本,最好绘制从相反方向开始的路径,而不是使用 transform:rotate(180deg) .

    复杂的转换似乎无法与 <textPath /> 一起正常工作.

    在这个答案中,我交换了路径的源和目标来表达 180 度的文本旋转。

编辑:为避免两条线相互碰撞,sweep flag SVG 路径弧已被修改。标志指示弧的方向。

console.log("D3 Forced Layout ready.")

////////////////////////////////////////////////////////////
//////////////////// D3 Forced Graph ///////////////////////
////////////////////////////////////////////////////////////

var data = {
"nodes": [{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4
},
{
"id": 5
}
],
"links": [{
"source": 1,
"target": 2,
"text": "this description is not centered"
},
{
"source": 2,
"target": 1,
"text": "Shorter description"
},
{
"source": 2,
"target": 3,
"text": "Shorter description"
},
{
"source": 3,
"target": 4,
"text": "even shorter"
},
{
"source": 4,
"target": 5,
"text": "shorter"
},
{
"source": 5,
"target": 1,
"text": "short"
}
]
}

initForceLayout()

function initForceLayout() {
let vw = 800
let vh = 800

const svg = d3.select("#chart").append("svg")
.attr("width", vw)
.attr("height", vh)

const forceLayout = svg.append("g")
.attr("id", "forceLayout")
.call(d3.zoom().on("zoom", function(event) {
svg.attr("transform", event.transform)
}))
.on("dblclick.zoom", null)

linksContainer = forceLayout.append("g").attr("class", "linkscontainer")
nodesContainer = forceLayout.append("g").attr("class", "nodesContainer")

var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(300))
.force('charge', d3.forceManyBody().strength(-400))
.force('center', d3.forceCenter(vw / 2, vh / 2))

link = linksContainer.selectAll("g")
.data(data.links)
.join("g")
.attr("cursor", "pointer")

linkLine = linksContainer.selectAll(".linkPath")
.data(data.links)
.join("path")
.attr("id", function(_, i) {
return "path" + i
})
.attr("stroke", "black")
.attr("opacity", 0.75)
.attr("stroke-width", 3)
.attr("fill", "transparent")

linkText = linksContainer.selectAll(".linkLabel")
.data(data.links)
.join("text")
.attr("dy", -10)
.attr("class", "linkLabel")
.attr("id", function(d, i) {
return "linkLabel" + i
})
// ANSWER EDIT: added text-anchor middle property
// so that the text is centered properly
.attr('text-anchor', 'middle')
.text("")

linkText.append("textPath")
.attr("xlink:href", function(_, i) {
return "#path" + i
})
.attr("opacity", 0.75)
.attr("cursor", "pointer")
.attr("class", "linkText")
.attr('startOffset', '50%')
.text(function(d) {
return d.text
}).each(function(d,i) {
// ANSWER EDIT: precalculate text width
var thisWidth = this.getComputedTextLength()
d3.select(this).attr('data-text-width', () => thisWidth)
})

node = nodesContainer.selectAll(".node")
.data(data.nodes, d => d.id)
.join("g")
.attr("class", "node")
.call(d3.drag()
.on("start", function(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
})
.on("drag", function(event, d) {
d.fx = event.x;
d.fy = event.y;
})
.on("end", function(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
})
)

node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 30)
.attr("fill", "whitesmoke")
.attr("stroke", "white")
.attr("stroke-width", 2)

simulation
.nodes(data.nodes)
.on("tick", function() {
// update link positions
linkLine.attr("d", function(d) {
const shouldInvert = d.target.x < d.source.x
if (!shouldInvert) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy)

return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
} else {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy)
// ANSWER EDIT: swapped source and target
// ANSWER EDIT2: changed sweep flag 1 to 0
return "M" + d.target.x + "," + d.target.y + "A" + dr + "," + dr + " 0 0,0 " + d.source.x + "," + d.source.y;
}
});

// update node positions
node
.attr("transform", function(d) {
return "translate(" + d.x + ", " + d.y + ")";
});
// ANSWER EDIT: removed 180 degree transform
// this was redundant
})

simulation
.force("link")
.links(data.links)

}
:root {
--bs-gradient-dark-right: #141727;
--bs-gradient-dark-left: #3a416f;
}

html,
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}

body {
background-color: lightgray;
overflow: hidden;
}

.border-radius-lg {
border-radius: 0.75rem;
}

.svg-container {
display: inline-block;
position: relative;
width: 100%;
padding-bottom: 100%;
/* aspect ratio */
vertical-align: top;
overflow: hidden;
}

#svg-content-responsive {
display: inline-block;
position: absolute;
top: 10px;
left: 0;
}

svg .rect {
fill: gold;
stroke: steelblue;
stroke-width: 5px;
}
<!DOCTYPE html>
<html>

<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>linkText</title>
<meta name="viewport" content="width=device-width, initial-scale=1">

<!-- D3.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js" charset="utf-8"></script>
</head>

<body>
<div id="chart"></div>
</body>

</html>

关于javascript - D3 调整linkText位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73810782/

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