javascript - 如何用D3.js创建一个圆组一个组织圆图像并用曲线连接?
问题描述
我正在尝试使用 d3.js 创建一个径向(圆形)分组圆,例如:
我写了一些如下代码。
但我不知道如何将每个圆圈与曲线连接起来,当鼠标悬停圆圈时,上面的圆圈会显示一个工具提示,怎么做?帮助将不胜感激。谢谢。
我更新了我的代码,现在我可以在一个大圆圈中绘制圆圈或图像元素。
const mockedData = {
"nodes": [
{
"name": "Node1one",
"label": "Node1",
"id": 1,
"x": 120,
"y": 120,
},
{
"name": "Node2",
"label": "Node2",
"id": 2,
"x": 350,
"y": 180,
},
]
}
const imgList = {
"images": [
{
"image": 'https://via.placeholder.com/30x30',
"x": -50,
},
{
"image": 'https://via.placeholder.com/30',
"x": 20
}
]
}
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
let { links, nodes } = mockedData;
let { images } = imgList;
const ticked = ( node) => {
node.attr("transform",
function (d) {return "translate(" + d.x + ", " + d.y + ")";});
}
const tickedImg = (nodeImg) => {
nodeImg.attr("x", function (d) {return d.x })
}
const node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
node.append('circle').attr("r", 86); //radius
svg.selectAll('circle')
.on('click', function () { // arrow function will produce this = undefined
d3.selectAll('circle')
.style("fill", "lightgray");
d3.select(this)
.style("fill", "aliceblue");
})
.on('mouseover', function () {
d3.selectAll('circle')
.style("stroke", "black");
d3.select(this)
.style("stroke", "green");
})
ticked( node )
const nodeText = node.append("text")
.attr("y", -70);
nodeText.selectAll("tspan.text")
.data((d) => d.name.split(" "))
.enter()
.append("tspan")
.attr("class", "text")
.text(d => d)
.attr("x", -30)
.attr("y", -60)
node.append("title")
.text(function (d) {return d.id;});
const nodeImg = node.selectAll("image")
.data(images)
.enter()
.append("image")
.attr("xlink:href", (d) => d.image)
.attr("width", 27)
.attr("height", 30)
tickedImg (nodeImg)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="100%" viewbox="0 0 2000 1000"></svg>
解决方案
好的,如果我们只想在图像之间绘制弯曲的弧线,我当然可以提供帮助:)
我在这里(非常详细地)回答了一个类似的问题:https ://stackoverflow.com/a/59784798/9792594 - 这应该有助于解释数学等。
我将只使用该答案中的最终功能,如果您需要有关其工作原理的背景知识,请查看该答案。
注意:您可以通过更改“绘制顺序”来更改 svg 元素的“z 顺序”,即在 DOM 中呈现它们的顺序。所以在d3中,使用.append()
,这只是意味着首先在底层调用你想要的代码,然后在下一层调用你想要的代码,依此类推。
const mockedData = {
"nodes": [
{
"name": "Node1",
"label": "Node1",
"id": 1,
"x": 120,
"y": 120,
},
{
"name": "Node2",
"label": "Node2",
"id": 2,
"x": 350,
"y": 180,
},
]
}
const imgList = {
"images": [
{
"image": 'https://via.placeholder.com/30x30',
"width": 30,
"height": 30,
"x": -50,
"y": -20
},
{
"image": 'https://via.placeholder.com/30',
"width": 30,
"height": 30,
"x": 20,
"y": -20
},
{
"image": 'https://via.placeholder.com/30',
"width": 30,
"height": 30,
"x": -15,
"y": 20
}
]
}
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
let { links, nodes } = mockedData;
let { images } = imgList;
const ticked = ( node) => {
node.attr("transform",
function (d) {return "translate(" + d.x + ", " + d.y + ")";});
}
const tickedImg = (nodeImg) => {
nodeImg.attr("x", function (d) {return d.x })
}
const node = svg.selectAll(".node")
.data(nodes);
node
.enter()
.append("path")
.attr("d", (d,i) => {
//console.log(d,i,images)
if (nodes.length > 1){
const j = i == (nodes.length - 1) ? 0 : i + 1;
const invertArc = (i+1) < (nodes.length/2);
const gtr2 = nodes.length > 2 ? invertArc : !invertArc;
//console.log(i,invertArc)
return pointsToPath(d, nodes[j], gtr2);
}
return "";
})
.attr("stroke", "black")
.attr("stroke-dasharray", "4")
.attr("fill", "transparent");
const nodeGroup = node
.enter()
.append("g")
.attr("class", "node");
const circle = nodeGroup.append('circle').attr("r", 86); //radius
circle
.style("fill", "darkgray")
.on('click', function () { // arrow function will produce this = undefined
d3.selectAll('circle')
.style("fill", "darkgray");
d3.select(this)
.style("fill", "aliceblue");
})
.on('mouseover', function () {
d3.selectAll('circle')
.style("stroke", "black");
d3.select(this)
.style("stroke", "green");
})
ticked( nodeGroup );
const nodeText = nodeGroup.append("text")
.attr("y", -70);
nodeText.selectAll("tspan.text")
.data((d) => d.name.split(" "))
.enter()
.append("tspan")
.attr("class", "text")
.text(d => d)
.attr("fill", "black")
.attr("x", -30)
.attr("y", -60)
nodeGroup.append("title")
.text(function (d) {return d.id;});
const nodeImg = nodeGroup.selectAll("image")
.data(images);
nodeImg
.enter()
.append("image")
.attr("xlink:href", d => d.image)
.attr("width", d => d.width)
.attr("height", d => d.height)
.attr("x", d => d.x)
.attr("y", d => d.y)
//tickedImg (nodeImg)
nodeImg
.enter()
.append("path")
.attr("d", (d,i) => {
//console.log(d,i,images)
if (images.length > 1){
const j = i == (images.length - 1) ? 0 : i + 1;
const invertArc = (i+1) < (images.length/2);
//console.log(i,invertArc)
return pointsToPath(d, images[j], invertArc);
}
return "";
})
.attr("stroke", "black")
.attr("stroke-dasharray", "4")
.attr("fill", "transparent")
.attr("transform", d => "translate(" + d.width/2 + ", " + d.height/2 + ")");
//tickedImg (nodeImg)
function pointsToPath(from, to, invertArc) {
const centerPoint = [ (from.x + to.x) / 2, (from.y + to.y) / 2];
const slope = (to.y - from.y) / (to.x - from.x);
const invSlope = -1 / slope;
const distance = Math.sqrt( Math.pow((to.x - from.x), 2) + Math.pow((to.y - from.y), 2) );
const offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);
const angle = Math.atan(slope);
const offsetY = Math.cos(angle)*offset;
const offsetX = Math.sin(angle)*offset;
const offsetCenter = [centerPoint[0] - offsetX, centerPoint[1] + offsetY];
const arcPointX = offsetCenter[0]
const arcPointY = offsetCenter[1]
return 'M' + from.x + ' ' + from.y + 'Q' + arcPointX + ' ' + arcPointY +
' ' + to.x + ' ' + to.y;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="100%" viewbox="0 0 1000 500">
<path d="M20 20 50 50" fill="transparent" stroke-width="5" stroke="black"></path>
</svg>
<script>
//the following d3 code would insert the same path as that manually inserted in the HTML above:
d3.select("svg")
.append("path")
//we can set it directly as below, or via a function:
.attr("d", "M20 20 50 50")
.attr("fill", "transparent")
.attr("stroke-width", "5")
.attr("stroke", "black");
</script>
输出(20 年 6 月 18 日更新):
推荐阅读
- odata - 未找到 Acumatica Odata 通用查询
- ios - 是否可以在 NSFetchedResultsController 的 NSSortDescriptor 中使用 UUID?
- django - 如何将参数传递给模型类?
- c++ - 为 QProgressBar 设置阈值
- angular - Angular Kendo Grid 使删除的主行的详细信息可见
- c - 如何在 C 中编写计时器函数?我尝试使用clock()函数
- javascript - 组合具有相同值/id 的对象并将总和添加到新对象
- python - 我有 12000 个已知 URL,用 Python 抓取它们的最快方法是什么?
- python - 将优势比公式应用于多个变量
- matlab - matlab:为绘图添加颜色维度