如何让所有的节点都围绕中心节点?
How to make all the nodes circle the center node?
我正在尝试制作一个力导向图,其中子节点和孙节点是 circling/orbiting 父节点。同时父节点连接到它的子节点,每个子节点连接到他们的每个孙节点。
视觉上看起来像这样:
我试过使用默认的力定向图(都是 here and there),但似乎无法像我尝试的那样在 circle/orbit 中整齐地排列它们制作.
我尝试查找 orbit code,但它似乎需要一种完全不同的方法。
这是我的 fiddle 和代码: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)
有什么想法吗?
通过使用 forceLink().distance
设置固定的 link 长度,并增加 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))
这是更新后的 fiddle
新的d3.forceRadial()
您需要的是 d3.forceRadial
,在 D3 v4.11 中引入。根据API,d3.forceRadial(radius[, x][, y])
会...
Create a new positioning force towards a circle of the specified radius centered at ⟨x,y⟩.
在你的例子中,我使用 level
来设置半径:
.force('radial', d3.forceRadial(function(d) {
return d.level * 50
}, width / 2, height / 2))
只有节点时,事情会更容易。但是,由于您在该力中有 link,因此您必须调整 link 力,直到获得所需的结果。
这是你的代码 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>
就像我说的,因为你有 links,所以事情有点复杂。如果您只有节点(此处与 d3.forceCollide
一起),看看 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')
.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:我把第一个节点的level设置为0
。
我正在尝试制作一个力导向图,其中子节点和孙节点是 circling/orbiting 父节点。同时父节点连接到它的子节点,每个子节点连接到他们的每个孙节点。
视觉上看起来像这样:
我试过使用默认的力定向图(都是 here and there),但似乎无法像我尝试的那样在 circle/orbit 中整齐地排列它们制作.
我尝试查找 orbit code,但它似乎需要一种完全不同的方法。
这是我的 fiddle 和代码: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)
有什么想法吗?
通过使用 forceLink().distance
设置固定的 link 长度,并增加 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))
这是更新后的 fiddle
新的d3.forceRadial()
您需要的是 d3.forceRadial
,在 D3 v4.11 中引入。根据API,d3.forceRadial(radius[, x][, y])
会...
Create a new positioning force towards a circle of the specified radius centered at ⟨x,y⟩.
在你的例子中,我使用 level
来设置半径:
.force('radial', d3.forceRadial(function(d) {
return d.level * 50
}, width / 2, height / 2))
只有节点时,事情会更容易。但是,由于您在该力中有 link,因此您必须调整 link 力,直到获得所需的结果。
这是你的代码 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>
就像我说的,因为你有 links,所以事情有点复杂。如果您只有节点(此处与 d3.forceCollide
一起),看看 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')
.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:我把第一个节点的level设置为0
。