我正在尝试制作一个力导向图,其中子孙节点正在绕着父母盘旋/轨道运行。同时,父节点连接到其子节点,并且每个子节点都连接到它们的每个孙子节点。
从视觉上看,它看起来像这样:
我已经尝试使用默认的力导向图(here和there)进行干预,但似乎没有办法像圆圈/轨道那样整齐地对它们进行排序。试图制作。
我尝试查找orbit code,但似乎需要采用完全不同的方法。
这是我的小提琴和代码:https://jsfiddle.net/znqkcLhs/
function getNeighbors(node) {
return links.reduce(function (neighbors, link) {
if (link.target.id === node.id) {
neighbors.push(link.source.id)
} else if (link.source.id === node.id) {
neighbors.push(link.target.id)
}
return neighbors
},
[node.id]
)
}
function isNeighborLink(node, link) {
return link.target.id === node.id || link.source.id === node.id
}
function getNodeColor(node, neighbors) {
if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
return node.level === 1 ? 'blue' : 'green'
}
return node.level === 1 ? 'red' : 'gray'
}
function getLinkColor(node, link) {
return isNeighborLink(node, link) ? 'green' : '#E5E5E5'
}
function getTextColor(node, neighbors) {
return Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1 ? 'green' : 'black'
}
var width = window.innerWidth
var height = window.innerHeight
var svg = d3.select('svg')
svg.attr('width', width).attr('height', height)
// simulation setup with all forces
var linkForce = d3
.forceLink()
.id(function (link) { return link.id })
.strength(function (link) { return link.strength })
var simulation = d3
.forceSimulation()
.force('link', linkForce)
.force('charge', d3.forceManyBody().strength(-120))
.force('center', d3.forceCenter(width / 2, height / 2))
var dragDrop = d3.drag().on('start', function (node) {
node.fx = node.x
node.fy = node.y
}).on('drag', function (node) {
simulation.alphaTarget(0.7).restart()
node.fx = d3.event.x
node.fy = d3.event.y
}).on('end', function (node) {
if (!d3.event.active) {
simulation.alphaTarget(0)
}
node.fx = null
node.fy = null
})
function selectNode(selectedNode) {
var neighbors = getNeighbors(selectedNode)
// we modify the styles to highlight selected nodes
nodeElements.attr('fill', function (node) { return getNodeColor(node, neighbors) })
textElements.attr('fill', function (node) { return getTextColor(node, neighbors) })
linkElements.attr('stroke', function (link) { return getLinkColor(selectedNode, link) })
}
var linkElements = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", function(link) { return link.value; })
.attr("stroke", "rgba(50, 50, 50, 0.2)")
var nodeElements = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 10)
.attr("fill", getNodeColor)
.call(dragDrop)
.on('click', selectNode)
var textElements = svg.append("g")
.attr("class", "texts")
.selectAll("text")
.data(nodes)
.enter().append("text")
.text(function (node) { return node.label })
.attr("font-size", 15)
.attr("dx", 15)
.attr("dy", 4)
simulation.nodes(nodes).on('tick', () => {
nodeElements
.attr('cx', function (node) { return node.x })
.attr('cy', function (node) { return node.y })
textElements
.attr('x', function (node) { return node.x })
.attr('y', function (node) { return node.y })
linkElements
.attr('x1', function (link) { return link.source.x })
.attr('y1', function (link) { return link.source.y })
.attr('x2', function (link) { return link.target.x })
.attr('y2', function (link) { return link.target.y })
})
simulation.force("link").links(links)
有什么想法吗?
答案 0 :(得分:3)
What you need is d3.forceRadial
, introduced in D3 v4.11. According to the API, d3.forceRadial(radius[, x][, y])
will...
Create a new positioning force towards a circle of the specified radius centered at ⟨x,y⟩.
In your case, I'm using level
to set the radius:
.force('radial', d3.forceRadial(function(d) {
return d.level * 50
}, width / 2, height / 2))
Things are easier when you have only nodes. However, since you have links in that force, you'll have to tweak the link force until you get the desired result.
Here is your code with d3.forceRadial
:
var nodes = [{
id: "pusat",
group: 0,
label: "pusat",
level: 0
}, {
id: "dki",
group: 1,
label: "dki",
level: 1
}, {
id: "jaksel",
group: 1,
label: "jaksel",
level: 3
}, {
id: "jakpus",
group: 1,
label: "jakpus",
level: 3
}, {
id: "jabar",
group: 2,
label: "jabar",
level: 1
}, {
id: "sumedang",
group: 2,
label: "sumedang",
level: 3
}, {
id: "bekasi",
group: 2,
label: "bekasi",
level: 3
}, {
id: "bandung",
group: 2,
label: "bandung",
level: 3
}, {
id: "jatim",
group: 3,
label: "jatim",
level: 1
}, {
id: "malang",
group: 3,
label: "malang",
level: 3
}, {
id: "lamongan",
group: 3,
label: "lamongan",
level: 3
}, {
id: "diy",
group: 4,
label: "diy",
level: 1
}, {
id: "sleman",
group: 4,
label: "sleman",
level: 3
}, {
id: "jogja",
group: 4,
label: "jogja",
level: 3
}, {
id: "bali",
group: 5,
label: "bali",
level: 1
}, {
id: "bali1",
group: 5,
label: "bali1",
level: 3
}, {
id: "bali2",
group: 5,
label: "bali2",
level: 3
}, {
id: "ntt",
group: 6,
label: "ntt",
level: 1
}, {
id: "ntt1",
group: 6,
label: "ntt1",
level: 3
}, {
id: "ntt2",
group: 6,
label: "ntt2",
level: 3
}]
var links = [{
target: "pusat",
source: "dki",
strength: 0.2,
value: 1
}, {
target: "pusat",
source: "jabar",
strength: 0.2,
value: 3
}, {
target: "pusat",
source: "jatim",
strength: 0.2,
value: 6
}, {
target: "pusat",
source: "diy",
strength: 0.2,
value: 1
}, {
target: "pusat",
source: "bali",
strength: 0.2,
value: 1
}, {
target: "pusat",
source: "ntt",
strength: 0.2,
value: 1
},
//{ target: "pusat", source: "malang" , strength: 0.2, value:3 },
//{ target: "pusat", source: "lamongan" , strength: 0.2, value:6 },
{
target: "dki",
source: "jaksel",
strength: 0.7,
value: 2
}, {
target: "dki",
source: "jakpus",
strength: 0.7,
value: 3
}, {
target: "jabar",
source: "sumedang",
strength: 0.7,
value: 0.5
}, {
target: "jabar",
source: "bekasi",
strength: 0.7,
value: 2
}, {
target: "jabar",
source: "bandung",
strength: 0.7,
value: 2
}, {
target: "jatim",
source: "malang",
strength: 0.7,
value: 3
}, {
target: "jatim",
source: "lamongan",
strength: 0.7,
value: 1
}, {
target: "diy",
source: "sleman",
strength: 0.7,
value: 3
}, {
target: "diy",
source: "jogja",
strength: 0.7,
value: 1
}, {
target: "bali",
source: "bali1",
strength: 0.7,
value: 1
}, {
target: "bali",
source: "bali2",
strength: 0.7,
value: 1
}, {
target: "ntt",
source: "ntt1",
strength: 0.7,
value: 1
}, {
target: "ntt",
source: "ntt2",
strength: 0.7,
value: 1
}
]
function getNeighbors(node) {
return links.reduce(function(neighbors, link) {
if (link.target.id === node.id) {
neighbors.push(link.source.id)
} else if (link.source.id === node.id) {
neighbors.push(link.target.id)
}
return neighbors
}, [node.id])
}
function isNeighborLink(node, link) {
return link.target.id === node.id || link.source.id === node.id
}
function getNodeColor(node, neighbors) {
if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
return node.level === 1 ? 'blue' : 'green'
}
return node.level === 1 ? 'red' : 'gray'
}
function getLinkColor(node, link) {
return isNeighborLink(node, link) ? 'green' : '#E5E5E5'
}
function getTextColor(node, neighbors) {
return Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1 ? 'green' : 'black'
}
var width = window.innerWidth
var height = window.innerHeight
var svg = d3.select('svg')
svg.attr('width', width).attr('height', height);
var circles = svg.selectAll(null)
.data([80,125])
.enter()
.append("circle")
.attr("cx", width/2)
.attr("cy", height/2)
.attr("r", d=>d)
.style("fill", "none")
.style("stroke", "#ccc");
// simulation setup with all forces
var linkForce = d3
.forceLink()
.id(function(link) {
return link.id
});
var simulation = d3
.forceSimulation()
.force('link', linkForce)
.force('charge', d3.forceManyBody().strength(-120))
.force('radial', d3.forceRadial(function(d) {
return d.level * 50
}, width / 2, height / 2))
.force('center', d3.forceCenter(width / 2, height / 2))
var dragDrop = d3.drag().on('start', function(node) {
node.fx = node.x
node.fy = node.y
}).on('drag', function(node) {
simulation.alphaTarget(0.7).restart()
node.fx = d3.event.x
node.fy = d3.event.y
}).on('end', function(node) {
if (!d3.event.active) {
simulation.alphaTarget(0)
}
node.fx = null
node.fy = null
})
function selectNode(selectedNode) {
var neighbors = getNeighbors(selectedNode)
// we modify the styles to highlight selected nodes
nodeElements.attr('fill', function(node) {
return getNodeColor(node, neighbors)
})
textElements.attr('fill', function(node) {
return getTextColor(node, neighbors)
})
linkElements.attr('stroke', function(link) {
return getLinkColor(selectedNode, link)
})
}
var linkElements = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", function(link) {
return link.value;
})
.attr("stroke", "rgba(50, 50, 50, 0.2)")
var nodeElements = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 10)
.attr("fill", getNodeColor)
.call(dragDrop)
.on('click', selectNode)
var textElements = svg.append("g")
.attr("class", "texts")
.selectAll("text")
.data(nodes)
.enter().append("text")
.text(function(node) {
return node.label
})
.attr("font-size", 15)
.attr("dx", 15)
.attr("dy", 4)
simulation.nodes(nodes).on('tick', () => {
nodeElements
.attr('cx', function(node) {
return node.x
})
.attr('cy', function(node) {
return node.y
})
textElements
.attr('x', function(node) {
return node.x
})
.attr('y', function(node) {
return node.y
})
linkElements
.attr('x1', function(link) {
return link.source.x
})
.attr('y1', function(link) {
return link.source.y
})
.attr('x2', function(link) {
return link.target.x
})
.attr('y2', function(link) {
return link.target.y
})
})
simulation.force("link").links(links)
<svg width="960" height="600">
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
As I said, because you have links, things are a bit more complicated. Look how d3.forceRadial
creates a nice radial pattern if you had only nodes (here, together with d3.forceCollide
):
var nodes = [{
id: "pusat",
group: 0,
label: "pusat",
level: 0
}, {
id: "dki",
group: 1,
label: "dki",
level: 1
}, {
id: "jaksel",
group: 1,
label: "jaksel",
level: 3
}, {
id: "jakpus",
group: 1,
label: "jakpus",
level: 3
}, {
id: "jabar",
group: 2,
label: "jabar",
level: 1
}, {
id: "sumedang",
group: 2,
label: "sumedang",
level: 3
}, {
id: "bekasi",
group: 2,
label: "bekasi",
level: 3
}, {
id: "bandung",
group: 2,
label: "bandung",
level: 3
}, {
id: "jatim",
group: 3,
label: "jatim",
level: 1
}, {
id: "malang",
group: 3,
label: "malang",
level: 3
}, {
id: "lamongan",
group: 3,
label: "lamongan",
level: 3
}, {
id: "diy",
group: 4,
label: "diy",
level: 1
}, {
id: "sleman",
group: 4,
label: "sleman",
level: 3
}, {
id: "jogja",
group: 4,
label: "jogja",
level: 3
}, {
id: "bali",
group: 5,
label: "bali",
level: 1
}, {
id: "bali1",
group: 5,
label: "bali1",
level: 3
}, {
id: "bali2",
group: 5,
label: "bali2",
level: 3
}, {
id: "ntt",
group: 6,
label: "ntt",
level: 1
}, {
id: "ntt1",
group: 6,
label: "ntt1",
level: 3
}, {
id: "ntt2",
group: 6,
label: "ntt2",
level: 3
}]
var links = [{
target: "pusat",
source: "dki",
strength: 0.2,
value: 1
}, {
target: "pusat",
source: "jabar",
strength: 0.2,
value: 3
}, {
target: "pusat",
source: "jatim",
strength: 0.2,
value: 6
}, {
target: "pusat",
source: "diy",
strength: 0.2,
value: 1
}, {
target: "pusat",
source: "bali",
strength: 0.2,
value: 1
}, {
target: "pusat",
source: "ntt",
strength: 0.2,
value: 1
},
//{ target: "pusat", source: "malang" , strength: 0.2, value:3 },
//{ target: "pusat", source: "lamongan" , strength: 0.2, value:6 },
{
target: "dki",
source: "jaksel",
strength: 0.7,
value: 2
}, {
target: "dki",
source: "jakpus",
strength: 0.7,
value: 3
}, {
target: "jabar",
source: "sumedang",
strength: 0.7,
value: 0.5
}, {
target: "jabar",
source: "bekasi",
strength: 0.7,
value: 2
}, {
target: "jabar",
source: "bandung",
strength: 0.7,
value: 2
}, {
target: "jatim",
source: "malang",
strength: 0.7,
value: 3
}, {
target: "jatim",
source: "lamongan",
strength: 0.7,
value: 1
}, {
target: "diy",
source: "sleman",
strength: 0.7,
value: 3
}, {
target: "diy",
source: "jogja",
strength: 0.7,
value: 1
}, {
target: "bali",
source: "bali1",
strength: 0.7,
value: 1
}, {
target: "bali",
source: "bali2",
strength: 0.7,
value: 1
}, {
target: "ntt",
source: "ntt1",
strength: 0.7,
value: 1
}, {
target: "ntt",
source: "ntt2",
strength: 0.7,
value: 1
}
]
function getNeighbors(node) {
return links.reduce(function(neighbors, link) {
if (link.target.id === node.id) {
neighbors.push(link.source.id)
} else if (link.source.id === node.id) {
neighbors.push(link.target.id)
}
return neighbors
}, [node.id])
}
function isNeighborLink(node, link) {
return link.target.id === node.id || link.source.id === node.id
}
function getNodeColor(node, neighbors) {
if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
return node.level === 1 ? 'blue' : 'green'
}
return node.level === 1 ? 'red' : 'gray'
}
function getLinkColor(node, link) {
return isNeighborLink(node, link) ? 'green' : '#E5E5E5'
}
function getTextColor(node, neighbors) {
return Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1 ? 'green' : 'black'
}
var width = window.innerWidth
var height = window.innerHeight
var svg = d3.select('svg')
.attr('width', width).attr('height', height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
// simulation setup with all forces
var simulation = d3.forceSimulation()
.force('radial', d3.forceRadial(function(d) {
return d.level * 50
}).strength(1))
.force('collide', d3.forceCollide().radius(35));
var dragDrop = d3.drag().on('start', function(node) {
node.fx = node.x
node.fy = node.y
}).on('drag', function(node) {
simulation.alphaTarget(0.7).restart()
node.fx = d3.event.x
node.fy = d3.event.y
}).on('end', function(node) {
if (!d3.event.active) {
simulation.alphaTarget(0)
}
node.fx = null
node.fy = null
})
function selectNode(selectedNode) {
var neighbors = getNeighbors(selectedNode)
// we modify the styles to highlight selected nodes
nodeElements.attr('fill', function(node) {
return getNodeColor(node, neighbors)
})
textElements.attr('fill', function(node) {
return getTextColor(node, neighbors)
})
linkElements.attr('stroke', function(link) {
return getLinkColor(selectedNode, link)
})
}
var linkElements = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr("stroke-width", function(link) {
return link.value;
})
.attr("stroke", "rgba(50, 50, 50, 0.2)")
var nodeElements = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 10)
.attr("fill", getNodeColor)
.call(dragDrop)
.on('click', selectNode)
var textElements = svg.append("g")
.attr("class", "texts")
.selectAll("text")
.data(nodes)
.enter().append("text")
.text(function(node) {
return node.label
})
.attr("font-size", 15)
.attr("dx", 15)
.attr("dy", 4)
simulation.nodes(nodes).on('tick', () => {
nodeElements
.attr('cx', function(node) {
return node.x
})
.attr('cy', function(node) {
return node.y
})
textElements
.attr('x', function(node) {
return node.x
})
.attr('y', function(node) {
return node.y
})
})
<svg width="600" height="500">
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
PS: I set the level of the first node to 0
.
答案 1 :(得分:2)
您可以使用forceLink().distance
来设置固定的链接长度,并增加forceManyBody().strength
,例如:
var linkForce = d3
.forceLink()
.id(function (link) { return link.id })
.distance(50)
.strength(1)
var simulation = d3
.forceSimulation()
.force('link', linkForce)
.force('charge', d3.forceManyBody().strength(-1000))
.force('center', d3.forceCenter(width / 2, height / 2))
这是一个更新的小提琴