javascript - d3 强制布局:d3 是否需要节点中的某些顺序才能正常工作?
问题描述
我正在制作一个图形可视化玩具项目。我希望能够给它一个边列表或邻接列表作为输入(在文本区域中),并在 svg 元素中向我展示相应的图形绘制。描述为边缘列表的图有效,但邻接列表以某种方式为所有节点提供相同的索引。
在过去的几天里,我一直在为一个问题头疼。我给出了相同的图表,当被描述为边缘列表时它工作正常......
,当被描述为邻接列表时,它给出了:
这是接受文本输入并将其转换为邻接/边缘列表的部分:
export const getAdjacencyListFromText = (text: string) => {
const tokenizedLines = getLines(text)
.map(line => getTokensFromLine(line).map(token => Number(token)))
.filter(tokens => tokens.length >= 2)
const adjacencyList = tokenizedLines.reduce<AdjacencyList>((acc: any, current: any) => {
const source = current[0]
const neighbors = current.slice(1)
acc[source] = neighbors
return acc
}, [])
return adjacencyList
}
export const getEdgeListFromText = (text: string) =>
getLines(text)
.map(line => getTokensFromLine(line).map(e => Number(e)))
.filter(lineTokens => lineTokens.length >= 2)
.map(tokens => {
const source = tokens[0]
const target = tokens[1]
const weight = tokens[2]
if (tokens.length === 2) return { source, target }
else if (tokens.length === 3) return { source, target, weight }
}) as EdgeList
创建图形对象的部分:
export const getGraphFromAdjacencyList = (adjacencyList: AdjacencyList) => {
const nodes = adjacencyList.reduce<G.Node>((acc: G.Node[], current: AdjacencyListEntry, index: number) => {
current.forEach(node => acc.push({ id: node }))
if (!acc.some(el => el.id === index)) acc.push({ id: index })
return acc
}, []) as G.Node[]
const links = nodes.reduce<G.Link>((acc: G.Link[], currentNode: G.Node) => {
const neighbors = adjacencyList[currentNode.id]
if (neighbors === undefined) return acc
const newLinks = neighbors
.map(neighborId => ({ source: currentNode.id, target: neighborId }))
.filter(link => !acc.some(l => l.source === link.source && l.target === link.target))
return acc = acc.concat(newLinks)
}, [])
return { nodes, links }
}
export const getGraphFromEdgeList = (edgeList: EdgeList) => {
const nodes = edgeList.reduce<G.Node>((acc: G.Node[], currentEdge) => {
const { source, target } = currentEdge
if (!acc.some(n => n.id === source)) {
acc.push({ id: source })
}
if (!acc.some(n => n.id === target)) {
acc.push({ id: target })
}
return acc
}, [])
return {
links: edgeList,
nodes,
}
}
以及我将该图提供给 d3 的部分:
componentDidUpdate() {
const { links: edgeData, nodes: nodeData } = this.props.graph
// newNodes and newLinks is done so that old nodes and links keep their position
// goal is better visuals
const previousNodes = this.simulation.nodes()
const newNodes = nodeData.map(node => {
const existingNode = _(previousNodes).find((n: any) => n.id === node.id)
if (existingNode !== undefined) {
return existingNode
} else {
return node
}
})
const previousLinks = this.simulation.force("link").links()
const newLinks = edgeData.map(edge => {
const existingLink = _(previousLinks).find((l: any) => l.source.id === edge.source && l.target.id === edge.target)
if (existingLink !== undefined) {
return existingLink
} else {
return edge
}
})
const line = this.edgeLayer.selectAll("g").data(newLinks)
const node = this.nodeLayer.selectAll("g").data(newNodes)
node.exit().remove()
line.exit().remove()
this.node = this.createNode(node)
this.edge = this.createEdge(line)
this.simulation = this.simulation.nodes(newNodes)
this.simulation.force("link").links(newLinks).id(d => d.id)
this.simulation.on("tick", this.simulationTick)
this.simulation.alphaTarget(1)
this.simulation.restart()
}
邻接列表中的图形对象是这样的:
{
nodes: [ { id: 2 }, { id: 1 }, { id: 3 } ],
links: [ { source: 2, target: 3 }, { source: 1, target: 2 } ] }
}
边缘列表中的图形对象是这样的:
{
nodes: [ { id: 1 }, { id: 2 }, { id: 3 } ],
links: [ { source: 1, target: 2 }, { source: 1, target: 3 } ]
}
唯一的区别是节点数组中节点的顺序。因此,在为我的边缘列表和邻接列表实用程序函数编写了一整个下午的测试用例之后,我决定在将节点提供给 d3 之前对其进行排序......并且它可以工作......
为什么顺序很重要?我去查看文档,并且没有任何地方(我到目前为止看到的)提到与节点顺序相关的任何重要性。我很困惑。
解决方案
好的,我相信我的图表是出于巧合在 EdgeList 案例中工作的。
似乎我应该selectAll
在我的函数中使用节点和链接,而tick
我应该使用它select
。我getAdjacencyListFromText
有时也会给出重复的节点/链接,我删除了那个错误。
结果:
simulationTick
simulationTick = () => {
const { width, height } = this.props
const keepBounded = (p: Point) => ({
...p,
x: Math.max(0, Math.min(p.x, width)),
y: Math.max(0, Math.min(p.y, height)),
})
const processPoint = (p: Point) => keepBounded(p)
const setEdgeAttributes = edge =>
edge
.attr("x1", d => processPoint(d.source).x)
.attr("y1", d => processPoint(d.source).y)
.attr("x2", d => processPoint(d.target).x)
.attr("y2", d => processPoint(d.target).y)
const setNodeAttributes = node =>
node.attr("transform", d => {
const p = processPoint(d)
return "translate(" + p.x + ", " + p.y + ")"
})
setEdgeAttributes(this.edge.select("line"))
setNodeAttributes(this.node)
}
内部createNode
函数,更改selectAll
为select
:
createNode = node => {
const nodeGroup = node
const nodeGroupEnter = nodeGroup.enter().append("g")
// here
const nodeCircle = nodeGroup.select("circle")
const nodeLabel = nodeGroup.select("text")
// here
const nodeCircleEnter = nodeGroupEnter.append("circle")
nodeCircleEnter(...)
const nodeLabelEnter = nodeGroupEnter.append("text")
nodeLabelEnter(...)
return nodeGroupEnter.merge(nodeGroup).attr("id", d => d.id)
}
相同的createEdge
:
createEdge = edge => {
const edgeGroup = edge
const edgeGroupEnter = edgeGroup.enter().append("g")
// here
const edgeLine = edgeGroup.select("line")
const edgeLabel = edgeGroup.select("text")
// here
const edgeLineEnter = edgeGroupEnter.append("line")
edgeLineEnter (...)
const edgeLabelEnter = edgeGroupEnter.append("text")
edgeLabelEnter (...)
return edgeGroupEnter.merge(edgeGroup)
}
推荐阅读
- vue.js - 非道具属性标签在样式起作用时不起作用
- apache-kafka - Kafka Producer 应重试 3 次以防失败
- excel - 试图获取剪贴板数据
- git - 如何与 React 项目共享 Github 存储库并隐藏您过去的提交?
- c# - C#表单在同时移动时闪烁
- sql - sql 查询应该退回的产品已经卖出了400次以上,退货超过了
- arrays - Swift:仅当数组匹配特定条件时如何执行操作?
- python-3.x - 如何检查目录访问属性(权限)?
- angular - 如何在圆环图中的值上设置最小宽度?
- kubernetes - 如何使自动集群升级工作 GKE/digitalocean 用于具有不同请求内存要求的作业类型?