I'm trying to blur out / lower the opacity of non-related links when dragging a particular node. So it only needs to highlight related links and nodes while dragging and blur out those that do not relate to the dragged node. It works if it's outside of a drag function, but it's not consistent when I keep it inside it. It just flashes randomly. Does it need to be happening in dragged?
const link = vis.selectAll("line")
.data(data.links)
.join("line")
.attr("stroke", 'grey')
.attr("fill", "red")
.attr("opacity", 0.2)
const drag = (simulation) => {
const dragstarted = (event) => {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
const dragged = (event) => {
event.subject.fx = event.x;
event.subject.fy = event.y;
node.on("mouseover", function(d) {
let thisNode = this.id;
console.log("🚀 ~ file: index.html ~ line 99 ~ node.on ~ thisNode", thisNode)
console.log("🚀 ~ file: index.html ~ line 106 ~ node.on ~ d", d)
link.attr("opacity", function(d) {
return (d.source.id == thisNode || d.target.id == thisNode) ? 1 : 0.2
});
});
// Set the stroke width back to normal when mouse leaves the node.
node.on('mouseout', function(d) {
link.attr('opacity', 0.2);
});
}
const dragended = (event) => {
if (!event.active) {simulation.alphaTarget(0)};
event.subject.fx = null;
event.subject.fy = null;
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
const node = vis.selectAll(".node")
.data(data.nodes)
.join("path")
.attr("class", "node")
.attr("d", d3.symbol().type(d3.symbolPlus).size(300))
.attr("id", d => d.id)
.attr("r", 10)
.attr("stroke", 'red')
.style("fill", "none")
.call(drag(simulation));
simulation.on("tick", () => {
text.attr("transform", (d) => {
return "translate(" + (d.x + 20) + "," + (d.y + 5) + ")";
});
node.attr("transform", (d) => {
return "translate(" + d.x + "," + d.y + ")";
});
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y)
})
let data ={"nodes":[{"id":"piquetures hp","group":"big"},{"id":"postcards hp","group":"big"},{"id":"instagram hp","group":"big"},{"id":"tumblr hp","group":"big"},{"id":"soundcloud","group":"big"},{"id":"pq about","group":"mid"},{"id":"pq playlists","group":"mid"},{"id":"pq videos","group":"mid"},{"id":"pq channels","group":"mid"},{"id":"pc about","group":"mid"},{"id":"pc videos","group":"mid"},{"id":"pc playlists","group":"mid"},{"id":"pc channels","group":"mid"},{"id":"tb about","group":"mid"},{"id":"passion project playlist","group":"less mid"},{"id":"other playlists","group":"less mid"},{"id":"old videos","group":"less mid"},{"id":"nz snacks","group":"low"},{"id":"macbook pro","group":"low"},{"id":"asia trip","group":"low"},{"id":"AoT","group":"low"},{"id":"names.","group":"low"},{"id":"vade","group":"low"},{"id":"nyc","group":"low"},{"id":"meet me","group":"low"},{"id":"doubleD","group":"low"},{"id":"nice day!","group":"low"},{"id":"k-pop","group":"low"},{"id":"4 lives","group":"low"},{"id":"adventure awaits","group":"low"},{"id":"jill vid","group":"low"},{"id":"chatty cny","group":"low"},{"id":"fan art","group":"low"},{"id":"bts","group":"low"},{"id":"ig xposts","group":"low"},{"id":"reblogged content","group":"low"},{"id":"fitness stories","group":"low"},{"id":"campaigns","group":"low"},{"id":"other channels","group":"less mid"},{"id":"online art portfolio","group":"less mid"},{"id":"angela","group":"low"}],"links":[{"source":"piquetures hp","target":"instagram hp","do I have to click to see":"","context":""},{"source":"instagram hp","target":"piquetures hp","do I have to click to see":"","context":""},{"source":"tumblr hp","target":"tb about","do I have to click to see":1,"context":"sidebar"},{"source":"tb about","target":"tumblr hp","do I have to click to see":"","context":""},{"source":"postcards hp","target":"piquetures hp","do I have to click to see":"","context":"we put it there"},{"source":"piquetures hp","target":"passion project playlist","do I have to click to see":"","context":"we put it there"},{"source":"passion project playlist","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"piquetures hp","target":"pq about","do I have to click to see":"","context":"youtube given"},{"source":"pq about","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"piquetures hp","target":"pq playlists","do I have to click to see":"","context":"youtube given"},{"source":"pq playlists","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"piquetures hp","target":"pq videos","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"piquetures hp","target":"pq channels","do I have to click to see":"","context":"youtube given"},{"source":"pq channels","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"piquetures hp","target":"AoT","do I have to click to see":"","context":"we put it there"},{"source":"piquetures hp","target":"names.","do I have to click to see":"","context":"we put it there"},{"source":"piquetures hp","target":"doubleD","do I have to click to see":"","context":"we put it there"},{"source":"piquetures hp","target":"meet me","do I have to click to see":"","context":"we put it there"},{"source":"piquetures hp","target":"other channels","do I have to click to see":"","context":"we put it there"},{"source":"piquetures hp","target":"asia trip","do I have to click to see":"","context":"we put it there"},{"source":"piquetures hp","target":"other playlists","do I have to click to see":"","context":"we put it there"},{"source":"AoT","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"AoT","target":"vade","do I have to click to see":1,"context":"within text"},{"source":"AoT","target":"names.","do I have to click to see":1,"context":"within text"},{"source":"AoT","target":"passion project playlist","do I have to click to see":1,"context":"within text"},{"source":"AoT","target":"bts","do I have to click to see":1,"context":"within text"},{"source":"AoT","target":"soundcloud","do I have to click to see":1,"context":"within text"},{"source":"AoT","target":"instagram hp","do I have to click to see":1,"context":"within text"},{"source":"AoT","target":"postcards hp","do I have to click to see":1,"context":"within text"},{"source":"AoT","target":"tumblr hp","do I have to click to see":1,"context":"within text"},{"source":"names.","target":"postcards hp","do I have to click to see":1,"context":"within text"},{"source":"names.","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"names.","target":"passion project playlist","do I have to click to see":1,"context":"within text"},{"source":"names.","target":"soundcloud","do I have to click to see":1,"context":"within text"},{"source":"names.","target":"instagram hp","do I have to click to see":1,"context":"within text"},{"source":"names.","target":"tumblr hp","do I have to click to see":1,"context":"within text"},{"source":"doubleD","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"doubleD","target":"meet me","do I have to click to see":1,"context":"within text"},{"source":"doubleD","target":"soundcloud","do I have to click to see":1,"context":"within text"},{"source":"doubleD","target":"instagram hp","do I have to click to see":1,"context":"within text"},{"source":"doubleD","target":"tumblr hp","do I have to click to see":1,"context":"within text"},{"source":"doubleD","target":"passion project playlist","do I have to click to see":1,"context":"within text"},{"source":"doubleD","target":"k-pop","do I have to click to see":2,"context":"end of video"},{"source":"doubleD","target":"piquetures hp","do I have to click to see":2,"context":"end of video"},{"source":"meet me","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"meet me","target":"instagram hp","do I have to click to see":1,"context":"within text"},{"source":"meet me","target":"soundcloud","do I have to click to see":1,"context":"within text"},{"source":"meet me","target":"AoT","do I have to click to see":2,"context":"end of video"},{"source":"meet me","target":"piquetures hp","do I have to click to see":2,"context":"end of video"},{"source":"4 lives","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"4 lives","target":"other channels","do I have to click to see":1,"context":"within text"},{"source":"4 lives","target":"instagram hp","do I have to click to see":1,"context":"within text"},{"source":"4 lives","target":"soundcloud","do I have to click to see":1,"context":"within text"},{"source":"4 lives","target":"online art portfolio","do I have to click to see":1,"context":"within text"},{"source":"4 lives","target":"old videos","do I have to click to see":1,"context":"within text"},{"source":"4 lives","target":"asia trip","do I have to click to see":1,"context":"within text"},{"source":"vade","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"vade","target":"angela","do I have to click to see":1,"context":"within text"},{"source":"vade","target":"instagram hp","do I have to click to see":1,"context":"within text"},{"source":"vade","target":"online art portfolio","do I have to click to see":1,"context":"within text"},{"source":"nyc","target":"other channels","do I have to click to see":1,"context":"within text"},{"source":"nyc","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"nyc","target":"instagram hp","do I have to click to see":1,"context":"within text"},{"source":"nyc","target":"soundcloud","do I have to click to see":1,"context":"within text"},{"source":"nice day!","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"nice day!","target":"other channels","do I have to click to see":1,"context":"within text"},{"source":"k-pop","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"adventure awaits","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"adventure awaits","target":"other channels","do I have to click to see":1,"context":"within text"},{"source":"piquetures hp","target":"4 lives","do I have to click to see":1,"context":"within embedded playlist"},{"source":"piquetures hp","target":"vade","do I have to click to see":1,"context":"within embedded playlist"},{"source":"piquetures hp","target":"nyc","do I have to click to see":1,"context":"within embedded playlist"},{"source":"piquetures hp","target":"nice day!","do I have to click to see":1,"context":"within embedded playlist"},{"source":"piquetures hp","target":"k-pop","do I have to click to see":1,"context":"within embedded playlist"},{"source":"piquetures hp","target":"adventure awaits","do I have to click to see":1,"context":"within embedded playlist"},{"source":"pq videos","target":"old videos","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"nz snacks","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"macbook pro","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"asia trip","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"AoT","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"names.","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"vade","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"nyc","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"meet me","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"doubleD","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"nice day!","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"k-pop","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"4 lives","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"adventure awaits","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"instagram hp","do I have to click to see":"","context":"at top"},{"source":"pq videos","target":"pq playlists","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"pq channels","do I have to click to see":"","context":"youtube given"},{"source":"pq videos","target":"pq about","do I have to click to see":"","context":"youtube given"},{"source":"pq playlists","target":"other playlists","do I have to click to see":"","context":"we put it there"},{"source":"pq playlists","target":"passion project playlist","do I have to click to see":"","context":"we put it there"},{"source":"pq playlists","target":"pq videos","do I have to click to see":"","context":"youtube given"},{"source":"pq playlists","target":"pq channels","do I have to click to see":"","context":"youtube given"},{"source":"pq playlists","target":"pq about","do I have to click to see":"","context":"youtube given"},{"source":"pq playlists","target":"asia trip","do I have to click to see":"","context":"we put it there"},{"source":"pq channels","target":"pq videos","do I have to click to see":"","context":"youtube given"},{"source":"pq channels","target":"pq playlists","do I have to click to see":"","context":"youtube given"},{"source":"pq channels","target":"pq about","do I have to click to see":"","context":"youtube given"},{"source":"pq channels","target":"other channels","do I have to click to see":"","context":"we put it there"},{"source":"pq about","target":"pq videos","do I have to click to see":"","context":"youtube given"},{"source":"pq about","target":"pq playlists","do I have to click to see":"","context":"youtube given"},{"source":"pq about","target":"pq channels","do I have to click to see":"","context":"youtube given"},{"source":"pq about","target":"instagram hp","do I have to click to see":"","context":"we put it there"},{"source":"pq about","target":"postcards hp","do I have to click to see":"","context":"we put it there"},{"source":"pq about","target":"instagram hp","do I have to click to see":"","context":"at top"},{"source":"pq channels","target":"instagram hp","do I have to click to see":"","context":"at top"},{"source":"pq playlists","target":"instagram hp","do I have to click to see":"","context":"at top"},{"source":"postcards hp","target":"jill vid","do I have to click to see":"","context":"we put it there"},{"source":"postcards hp","target":"chatty cny","do I have to click to see":"","context":"we put it there"},{"source":"postcards hp","target":"pc about","do I have to click to see":"","context":"youtube given"},{"source":"postcards hp","target":"pc videos","do I have to click to see":"","context":"youtube given"},{"source":"postcards hp","target":"pc playlists","do I have to click to see":"","context":"youtube given"},{"source":"postcards hp","target":"pc channels","do I have to click to see":"","context":"youtube given"},{"source":"pc videos","target":"pc about","do I have to click to see":"","context":"youtube given"},{"source":"pc videos","target":"pc playlists","do I have to click to see":"","context":"youtube given"},{"source":"pc videos","target":"pc channels","do I have to click to see":"","context":"youtube given"},{"source":"pc videos","target":"jill vid","do I have to click to see":"","context":"youtube given"},{"source":"pc videos","target":"chatty cny","do I have to click to see":"","context":"youtube given"},{"source":"pc videos","target":"piquetures hp","do I have to click to see":"","context":"at top"},{"source":"pc playlists","target":"pc about","do I have to click to see":"","context":"youtube given"},{"source":"pc playlists","target":"pc videos","do I have to click to see":"","context":"youtube given"},{"source":"pc playlists","target":"pc channels","do I have to click to see":"","context":"youtube given"},{"source":"pc playlists","target":"piquetures hp","do I have to click to see":"","context":"at top"},{"source":"pc channels","target":"piquetures hp","do I have to click to see":"","context":"we put it there"},{"source":"pc channels","target":"pc about","do I have to click to see":"","context":"youtube given"},{"source":"pc channels","target":"pc videos","do I have to click to see":"","context":"youtube given"},{"source":"pc channels","target":"pc playlists","do I have to click to see":"","context":"youtube given"},{"source":"pc channels","target":"piquetures hp","do I have to click to see":"","context":"at top"},{"source":"pc about","target":"pc channels","do I have to click to see":"","context":"youtube given"},{"source":"pc about","target":"pc videos","do I have to click to see":"","context":"youtube given"},{"source":"pc about","target":"pc playlists","do I have to click to see":"","context":"youtube given"},{"source":"pc about","target":"piquetures hp","do I have to click to see":"","context":"at top"},{"source":"pc about","target":"piquetures hp","do I have to click to see":"","context":"we put it there"},{"source":"pc about","target":"instagram hp","do I have to click to see":"","context":"we put it there"},{"source":"pc about","target":"piquetures hp","do I have to click to see":"","context":"we put it there"},{"source":"jill vid","target":"piquetures hp","do I have to click to see":1,"context":"within text"},{"source":"jill vid","target":"instagram hp","do I have to click to see":1,"context":"within text"},{"source":"jill vid","target":"tumblr hp","do I have to click to see":1,"context":"within text"},{"source":"jill vid","target":"soundcloud","do I have to click to see":1,"context":"within text"},{"source":"chatty cny","target":"piquetures hp","do I have to click to see":1,"context":"within text"},{"source":"chatty cny","target":"instagram hp","do I have to click to see":1,"context":"within text"},{"source":"chatty cny","target":"soundcloud","do I have to click to see":1,"context":"within text"},{"source":"chatty cny","target":"names.","do I have to click to see":2,"context":"end of video"},{"source":"chatty cny","target":"piquetures hp","do I have to click to see":2,"context":"end of video"},{"source":"passion project playlist","target":"AoT","do I have to click to see":"","context":"youtube given"},{"source":"passion project playlist","target":"names.","do I have to click to see":"","context":"youtube given"},{"source":"passion project playlist","target":"doubleD","do I have to click to see":"","context":"youtube given"},{"source":"passion project playlist","target":"meet me","do I have to click to see":"","context":"youtube given"},{"source":"passion project playlist","target":"4 lives","do I have to click to see":"","context":"youtube given"},{"source":"passion project playlist","target":"vade","do I have to click to see":"","context":"youtube given"},{"source":"passion project playlist","target":"nyc","do I have to click to see":"","context":"youtube given"},{"source":"passion project playlist","target":"nice day!","do I have to click to see":"","context":"youtube given"},{"source":"passion project playlist","target":"k-pop","do I have to click to see":"","context":"youtube given"},{"source":"passion project playlist","target":"adventure awaits","do I have to click to see":"","context":"youtube given"},{"source":"passion project playlist","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"asia trip","target":"piquetures hp","do I have to click to see":"","context":"youtube given"},{"source":"instagram hp","target":"piquetures hp","do I have to click to see":"","context":"we put it there"},{"source":"campaigns","target":"instagram hp","do I have to click to see":"","context":"instagram given"},{"source":"tumblr hp","target":"fan art","do I have to click to see":"","context":"we put it there"},{"source":"tumblr hp","target":"bts","do I have to click to see":"","context":"we put it there"},{"source":"tumblr hp","target":"ig xposts","do I have to click to see":"","context":"we put it there"},{"source":"tumblr hp","target":"reblogged content","do I have to click to see":"","context":"we put it there"},{"source":"bts","target":"jill vid","do I have to click to see":"","context":"within text"},{"source":"reblogged content","target":"tumblr hp","do I have to click to see":"","context":"we put it there"},{"source":"bts","target":"tumblr hp","do I have to click to see":"","context":"we put it there"},{"source":"ig xposts","target":"tumblr hp","do I have to click to see":"","context":"we put it there"},{"source":"fan art","target":"tumblr hp","do I have to click to see":"","context":"we put it there"},{"source":"tb about","target":"instagram hp","do I have to click to see":"","context":"we put it there"},{"source":"tb about","target":"piquetures hp","do I have to click to see":"","context":"we put it there"},{"source":"tb about","target":"postcards hp","do I have to click to see":"","context":"we put it there"},{"source":"tb about","target":"soundcloud","do I have to click to see":"","context":"we put it there"},{"source":"ig xposts","target":"AoT","do I have to click to see":"","context":"within text"},{"source":"instagram hp","target":"campaigns","do I have to click to see":"","context":"instagram given"},{"source":"instagram hp","target":"fitness stories","do I have to click to see":"","context":"instagram given"},{"source":"fitness stories","target":"instagram hp","do I have to click to see":"","context":"instagram given"}]}
// SVG dimensions set to full width and height of the screen
const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
const height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
const drawChart = (data) => {
// Create detached <svg> element.
const vis = d3.select('.main_container')
.append("svg")
.attr("width", width)
.attr("height", height);
const g = vis.append("g")
const simulation = d3.forceSimulation(data.nodes).force("charge", d3.forceManyBody().strength(-300)).force("link", d3.forceLink(data.links)
.id(d => d.id)
.distance(300)).force("center", d3.forceCenter(width / 2, height / 2)).force("collide", d3.forceCollide().strength(10))
const link = vis.selectAll("line")
.data(data.links)
.join("line")
.attr("stroke", 'grey')
.attr("fill", "red")
.attr("opacity", 0.2)
const drag = (simulation) => {
const dragstarted = (event) => {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
const dragged = (event) => {
event.subject.fx = event.x;
event.subject.fy = event.y;
node.on("mouseover", function(d) {
let thisNode = this.id;
console.log("🚀 ~ file: index.html ~ line 99 ~ node.on ~ thisNode", thisNode)
console.log("🚀 ~ file: index.html ~ line 106 ~ node.on ~ d", d)
link.attr("opacity", function(d) {
return (d.source.id == thisNode || d.target.id == thisNode) ? 1 : 0.2
});
});
// Set the stroke width back to normal when mouse leaves the node.
node.on('mouseout', function(d) {
link.attr('opacity', 0.2);
});
}
const dragended = (event) => {
if (!event.active) {simulation.alphaTarget(0)};
event.subject.fx = null;
event.subject.fy = null;
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
const node = vis.selectAll(".node")
.data(data.nodes)
.join("path")
.attr("class", "node")
.attr("d", d3.symbol().type(d3.symbolPlus).size(300))
.attr("id", d => d.id)
.attr("r", 10)
.attr("stroke", 'red')
.style("fill", "none")
.call(drag(simulation));
const text = vis.append("g")
.attr("class", "labels")
.selectAll("text")
.data(data.nodes)
.join("text")
.text(d => d.id);
simulation.on("tick", () => {
text.attr("transform", (d) => {
return "translate(" + (d.x + 20) + "," + (d.y + 5) + ")";
});
node.attr("transform", (d) => {
return "translate(" + d.x + "," + d.y + ")";
});
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y)
})
// zoom block
const handleZoom = (e) => {
d3.select('svg g')
.attr('transform', e.transform);
}
const zoom = d3.zoom()
.scaleExtent([0.1, 3.5]) //[k0, k1] where k0 is the minimum allowed scale factor and k1 is the maximum allowed scale factor, and returns this zoom behavior. If extent is not specified, returns the current scale extent, which defaults to [0, ∞]. The scale extent restricts zooming in and out.
// .translateExtent([
// [0, 0],
// [width, height]
// ]) // constrain the zoom and pan so that the it can only zoom and pan within specified bounds
.on('zoom', handleZoom);
d3.select('.main_container')
.call(zoom);
}
drawChart(data);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>
<div class="main_container"></div>
dragged
is called throughout the drag so it makes sense to update link opacity in the dragstarted
and dragend
i.e. the minimum number of times.
E.g. in dragstarted
run the test on whether links are connected and update the opacity appropriately (e.g. 1 for connected links, 0.1 for non-connected links) :
const dragstarted = (event) => {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
// update link opacity to 0.1 for non-connected nodes
link.each(function(d) {
const test = d.source.id === event.subject.id || d.target.id === event.subject.id;
d3.select(this).attr("opacity", test ? 1 : 0.1);
});
}
Then in dragended
you can revert the links to their initial state of opacity:
const dragended = (event) => {
if (!event.active) {
simulation.alphaTarget(0)
};
event.subject.fx = null;
event.subject.fy = null;
// reset link opacity to 1
link.attr("opacity", 1);
}
I updated a couple of items in your code: data
for brevity, link length for demo purposes, having opacity reduce from 1 to 0.1 (per your question) and using circles not crosses for the node as the crosses seem very difficult to manipulate. Otherwise the working example is per your question:
// SVG dimensions set to full width and height of the screen
const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
const height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
const drawChart = (data) => {
// Create detached <svg> element.
const vis = d3.select('.main_container')
.append("svg")
.attr("width", width)
.attr("height", height);
const g = vis.append("g");
const simulation = d3.forceSimulation(data.nodes)
.force("charge", d3.forceManyBody().strength(-300))
.force("link", d3.forceLink(data.links).id(d => d.id).distance(100)) // reduced from 300 for demo purpose
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collide", d3.forceCollide().strength(10))
const link = vis.selectAll("line")
.data(data.links)
.join("line")
.attr("stroke", 'grey')
.attr("fill", "red")
.attr("opacity", 1) // changed to 1 so we can lower opacity
const drag = (simulation) => {
const dragstarted = (event) => {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
// update link opacity to 0.1 for non-connected nodes
link.each(function(d) {
const test = d.source.id === event.subject.id || d.target.id === event.subject.id;
d3.select(this).attr("opacity", test ? 1 : 0.1);
});
}
const dragged = (event) => {
event.subject.fx = event.x;
event.subject.fy = event.y;
}
const dragended = (event) => {
if (!event.active) {
simulation.alphaTarget(0)
};
event.subject.fx = null;
event.subject.fy = null;
// reset link opacity to 1
link.attr("opacity", 1);
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
// for demo purpose replaced cross with circle
// as the crosses are difficult to drag
const node = vis.selectAll(".node")
.data(data.nodes)
//.join("path")
//.attr("class", "node")
//.attr("d", d3.symbol().type(d3.symbolPlus).size(300))
//.attr("id", d => d.id)
//.attr("r", 10)
//.attr("stroke", 'red')
//.style("fill", "none")
.join("circle")
.attr("class", "node")
.attr("id", d => d.id)
.attr("r", 8)
.style("fill", "red")
.call(drag(simulation));
const text = vis.append("g")
.attr("class", "labels")
.selectAll("text")
.data(data.nodes)
.join("text")
.text(d => d.id);
simulation.on("tick", () => {
text.attr("transform", (d) => {
return "translate(" + (d.x + 20) + "," + (d.y + 5) + ")";
});
node.attr("transform", (d) => {
return "translate(" + d.x + "," + d.y + ")";
});
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y)
})
// zoom block
const handleZoom = (e) => {
d3.select('svg g')
.attr('transform', e.transform);
}
const zoom = d3.zoom()
.scaleExtent([0.1, 3.5]) //[k0, k1] where k0 is the minimum allowed scale factor and k1 is the maximum allowed scale factor, and returns this zoom behavior. If extent is not specified, returns the current scale extent, which defaults to [0, ∞]. The scale extent restricts zooming in and out.
// .translateExtent([
// [0, 0],
// [width, height]
// ]) // constrain the zoom and pan so that the it can only zoom and pan within specified bounds
.on('zoom', handleZoom);
d3.select('.main_container')
.call(zoom);
}
drawChart(data);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>
<div class="main_container"></div>
<script>
const data = {
"nodes": [
{ "id": "A", "group": "big" },
{ "id": "B", "group": "big" },
{ "id": "C", "group": "big" },
{ "id": "D", "group": "big" },
{ "id": "E", "group": "big" }
],
"links": [
{ "source": "A", "target": "B" },
{ "source": "A", "target": "C" },
{ "source": "B", "target": "D" },
{ "source": "B", "target": "E" }
]
}
</script>