I've used D3 to create a force directed graph with dynamic fields. It's currently has zoom installed, but I'm looking to remove the zoom but keep the pan. I've seen the use of xyzoom but I'm unsure how I can translate this into the code or if there's any better alternatives versions available Any advice would be much appreciated
<svg width="100%" height="100%"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var baseNodes = [
//Base node here
]
var baseLinks = [
//Base links here
]
var nodes = [...baseNodes]
var links = [...baseLinks]
function getNeighbors(node) {
return baseLinks.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", 1920)
.attr("height", 1080)
.call(d3.zoom().on("zoom", function () {
svg.attr("transform", d3.event.transform)
}))
.append("g")
var linkElements,
nodeElements,
textElements
var linkGroup = svg.append('g').attr('class', 'links')
var nodeGroup = svg.append('g').attr('class', 'nodes')
var textGroup = svg.append('g').attr('class', 'texts')
var selectedId
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(-2000))
.force('center', d3.forceCenter(width / 1.5, height / 1.5))
var dragDrop = d3.drag().on('start', function (node) {
node.fx = node.x
node.fy = node.y
}).on('drag', function (node) {
simulation.alphaTarget(1).restart()
node.fx = d3.event.x
node.fy = d3.event.y
}).on('end', function (node) {
if (!d3.event.active) {
simulation.alphaTarget(0.05)
}
node.fx = null
node.fy = null
})
function selectNode(selectedNode) {
if (selectedId === selectedNode.id) {
selectedId = undefined
resetData()
updateSimulation()
} else {
selectedId = selectedNode.id
updateData(selectedNode)
updateSimulation()
}
var neighbors = getNeighbors(selectedNode)
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) })
}
function resetData() {
var nodeIds = nodes.map(function (node) { return node.id })
baseNodes.forEach(function (node) {
if (nodeIds.indexOf(node.id) === -1) {
nodes.push(node)
}
})
links = baseLinks
}
function updateData(selectedNode) {
var neighbors = getNeighbors(selectedNode)
var newNodes = baseNodes.filter(function (node) {
return neighbors.indexOf(node.id) > -1 || node.level === 1
})
var diff = {
removed: nodes.filter(function (node) { return newNodes.indexOf(node) === -1 }),
added: newNodes.filter(function (node) { return nodes.indexOf(node) === -1 })
}
diff.removed.forEach(function (node) { nodes.splice(nodes.indexOf(node), 1) })
diff.added.forEach(function (node) { nodes.push(node) })
links = baseLinks.filter(function (link) {
return link.target.id === selectedNode.id || link.source.id === selectedNode.id
})
}
function updateGraph() {
linkElements = linkGroup.selectAll('line')
.data(links, function (link) {
return link.target.id + link.source.id
})
linkElements.exit().remove()
var linkEnter = linkElements
.enter().append('line')
.attr('stroke-width', 1)
.attr('stroke', 'rgba(50, 50, 50, 0.2)')
linkElements = linkEnter.merge(linkElements)
nodeElements = nodeGroup.selectAll('circle')
.data(nodes, function (node) { return node.id })
nodeElements.exit().remove()
var nodeEnter = nodeElements
.enter()
.append('circle')
.attr('r', 5)
.attr('fill', function (node) { return node.level === 1 ? 'red' : 'gray' })
.call(dragDrop)
.on('click', selectNode)
nodeElements = nodeEnter.merge(nodeElements)
textElements = textGroup.selectAll('text')
.data(nodes, function (node) { return node.id })
textElements.exit().remove()
var textEnter = textElements
.enter()
.append('text')
.text(function (node) { return node.label })
.attr('font-size', 16)
.attr('dx', 20)
.attr('dy', 16)
textElements = textEnter.merge(textElements)
}
function updateSimulation() {
updateGraph()
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)
simulation.alphaTarget(0.05).restart()
}
updateSimulation()
</script>
You can turn off zooming using
scaleExtent().Reference: https://d3js.org/d3-zoom#zoom_scaleExtent
Example: