D3 在拖动特定节点时模糊/降低不相关链接的不透明度

D3 blur out / lower the opacity of non-related links when dragging a particular node

我试图在拖动特定节点时模糊/降低不相关链接的不透明度。所以拖拽时只需要高亮显示相关链接和节点,模糊掉与被拖拽节点无关的链接和节点即可。如果它在拖动功能之外,它会起作用,但当我将它放在其中时,它会不一致。它只是随机闪烁。是否需要在拖动中发生?

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 在整个拖动过程中被调用,因此在 dragstarteddragend 中更新 link 不透明度是有意义的,即最小次数。

例如在 dragstarted 运行 中测试 link 是否已连接并适当更新不透明度(例如,连接的 link 为 1,non-connected 为 0.1 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);
  });
}

然后在 dragended 中,您可以将 link 恢复到初始不透明状态:

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);
}

我更新了您代码中的几个项目:data 为简洁起见,link 长度用于演示目的,不透明度从 1 降低到 0.1(根据您的问题)并使用圆圈而不是十字对于节点,因为十字线似乎很难操纵。否则工作示例是根据您的问题:

// 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>