SOURCE

console 命令行工具 X clear

                    
>
console
const devices = new Array(5).fill("").map((d,i)=>`device${i+1}`),
n = devices.length,
interval = 100,
 rectwidth = 30,
 radius=25,
 linkRaduis=7;


 function dragstarted(d) {
     const g = d3.select(d3.select(this).node().parentNode);
    g.raise().attr("stroke", "black");
  }

  function dragged(d) {
    const g = d3.select(d3.select(this).node().parentNode);
    d.x = d3.event.x;
    d.y = d3.event.y;
    g.select('rect').attr("x", d.x-rectwidth/2).attr("y", d.y - rectwidth/2);
    g.select('text').attr("x", d.x ).attr("y", d.y+rectwidth);
    g.select('.outerCircle').attr("cx", d.x ).attr("cy", d.y);
    g.select('.dragCircle').attr("cx", d.x ).attr("cy", d.type === "HSM" ?d.y+radius :  d.y-radius);
    d3.selectAll("path").each(function(link){
        const linkEle = d3.select(this);
        if(link.source.name === d.name ||link.target.name === d.name){
            // const p = d3.path();
            // p.moveTo(d.x,d.y+radius+linkRaduis);
            // p.lineTo(link.target.x,link.target.y-radius);
           linkEle.attr("d",d=>{
               const source = d.source,
                target = d.target;
                if(source.type === "HSM"){
                    return `M${source.x} ${source.y+radius+linkRaduis} L${target.x} ${target.y - radius - linkRaduis}`
                }else if(source.type !== "HSM" && target.type !== "HSM") {
                    return `M${source.x} ${source.y - radius -linkRaduis} L${target.x} ${target.y - radius - linkRaduis}`
                }else{
                    return `M${source.x} ${source.y - radius -linkRaduis} L${target.x} ${target.y + radius + linkRaduis}`
                }
           });
        }
        // else if(link.target.name === d.name){
        //     const p = d3.path();
        //     p.moveTo(link.source.x,link.source.y+radius+linkRaduis);
        //     p.lineTo(d.x,d.y-radius-linkRaduis);
        //    linkEle.attr("d",p.toString());
        // }
    })
  }

  function dragended(d) {
    const g = d3.select(d3.select(this).node().parentNode);
    g.raise().attr("stroke", null);
  }


   function dragstartedEdge(d) {
        // d3.select(this).attr("r",rectwidth+5 );
        const container = d3.select(".container");
        const linksContainer = container.select(".links")
    //    linksContainer.select(".add").remove();
        linksContainer.append("path")
                .classed("add",true)
                .style("fill","none")
                .style("stroke","#000");
  }

  function draggedEdge(d) {
    const addPath = d3.select(".add");
    const circle = d3.select(this);
    // d.x = d3.event.x;
    // d.y = d3.event.y;
    const cx = circle.attr("cx"),cy = circle.attr("cy");
    // const p = d3.path();
    //  p.moveTo(d.sourceX,d.sourceY)
    //  p.lineTo(d3.event.x,d3.event.y);    
    if(d.type === "HSM"){
        addPath.attr("d",`M${+cx} ${+cy+linkRaduis} L${d3.event.x} ${d3.event.y + radius + linkRaduis }`);
    }else{
        addPath.attr("d",`M${+cx} ${+cy-linkRaduis} L${d3.event.x} ${d3.event.y - radius - linkRaduis }`);
    }
   
  }


  function dragendedEdge(d) {
    // d3.select(this).attr("r",rectwidth);
    const {x,y} = d3.event;
    let cy;
    const cx = d3.event.x;
    if(d.type === "HSM"){
        cy = d3.event.y + radius + linkRaduis;
    }else{
        cy = d3.event.y - radius - linkRaduis;
    }
    const targets = d3.selectAll(".dragCircle");
    targets.each(function(tar){
        if(d.name !== tar.name){
            const t_circle = d3.select(this);
            const t_cx = +t_circle.attr("cx"),t_cy = +t_circle.attr("cy"); 
            const dis = Math.abs(Math.sqrt(Math.pow(cx-t_cx,2)+Math.pow(cy-t_cy,2))); 
            if(dis< (linkRaduis+3)){
                   const container = d3.select(".container");
                const linksContainer = container.select(".links")
                linksContainer.append("path")
                        .datum({
                                    source:d,
                                    target:tar
                                })
                        
                        .attr("d",d=>{
                            const source = d.source,
                            target = d.target;
                            if(source.type === "HSM"){
                                return `M${source.x} ${source.y+radius+linkRaduis} L${target.x} ${target.y - radius - linkRaduis}`
                            }else if(source.type !== "HSM" && target.type !== "HSM") {
                                return `M${source.x} ${source.y - radius -linkRaduis} L${target.x} ${target.y - radius - linkRaduis}`
                            }else{
                                return `M${source.x} ${source.y - radius -linkRaduis} L${target.x} ${target.y + radius + linkRaduis}`
                            }
                        })
                       .call(link=>giveLinkAttr(link))
            }
        }

     })
   
      d3.select(".add").remove();
        
  }

const dragNode = d3.drag()
    .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended);

function container(){
    console.log(this)
    return this;
}
const dragEdge = d3.drag()
      .on("start", dragstartedEdge)
      .on("drag", draggedEdge)
      .on("end", dragendedEdge)

const generateData = _=>{
    const root = {name:"HSM",x:0,y:0,type:"HSM"};
    let nodes,links;
    if(devices.length%2 ===0){
        //设备的数量是偶数
        nodes =  devices.map((d,i)=>({
           name:d,
           x: (i-n/2)*interval + interval/2,
           y:100
        }))
    }else{
        //设备的数量是奇数
        nodes =  devices.map((d,i)=>({
           name:d,
           x: (i-(n-1)/2)*interval,
           y:150
        }))
    }
     links = nodes.map(d=>({
        source:root,
        target:d
    }))
    return {nodes:[root,...nodes],links}
}

const giveLinkAttr = link=>{
    link.style("fill","none")
        // .style("stroke","#000")
        .classed("link",true)
        .style("stroke-width","1.5px")
        .on("mouseover",d=>{
            const line = d3.select(d3.event.target);
            line.style("stroke-width","3px");
        })
        .on("mouseout",d=>{
            const line = d3.select(d3.event.target);
            line.style("stroke-width","1.5px");
        })
        .on("click",d=>{
             d3.event.preventDefault();
            d3.event.stopPropagation();
            const line = d3.select(d3.event.target);
            // line.style("stroke","#09c")
            line.classed("selected",true);
        })
}

const init = _ =>{
    const width = 750,height=800;
    const svg = d3.select(svgContainer).attr("width",width).attr("height",height);
    const container = svg.append("g")
        .classed("container",true)
        .attr("transform",`translate(${width/2},50)`)

    container.append("g").classed("links",true)
                
}
const draw = _=>{
    const container = d3.select(".container");
    const linksContainer = container.select(".links")
    const {nodes,links} = generateData();


     linksContainer.selectAll(".link")
        .data(links)
        .enter()
        .append("path")
        .attr("d",d=>{
            const source = d.source,
            target = d.target;
            return `M${source.x} ${source.y+radius+linkRaduis}L${target.x} ${target.y - radius - linkRaduis}`
        })
        .call(link=>giveLinkAttr(link))
       
    
    container.selectAll(".node")
        .data(nodes)
        .enter()
        .append("g")
        .classed("node",true)
        .call(g=>g.append("circle")
                    .attr("r",linkRaduis) 
                    .attr("cx",d=>  d.x) 
                    .attr("cy",d=> d.type === "HSM" ? d.y+radius : d.y-radius)
                    .style("fill","#09c")
                    .classed("dragCircle",true)
                    .call(dragEdge)
                    .style("cursor","pointer")
                    // .style("fill-opacity","0.4")
                    .on("mouseover",d=>{
                        d3.select(d3.event.target).attr("r",linkRaduis+3) 
                    })
                    .on("mouseout",d=>{
                        d3.select(d3.event.target).attr("r",linkRaduis) 
                    }))
        .call(g=>g.append("circle")
                    .attr("r",radius) 
                    .attr("cx",d=>d.x) 
                    .attr("cy",d=>d.y)
                    .style("fill","#fff")
                    .style("stroke","#000")
                    .classed("outerCircle",true)
                     .call(dragNode)
                    .style("cursor","pointer"))
        .call(g=>g.append("rect")
                    .attr("width",rectwidth) 
                    .attr("height",rectwidth) 
                    .attr("x",d=>d.x-rectwidth/2) 
                    .attr("y",d=>d.y-rectwidth/2)
                    .style("fill","lightpink")
                    .style("pointer-events","none")
                    .style("cursor","pointer"))
        .call(g=>g.append("text")
                    .attr("x",d=>d.x) 
                    .attr("y",d=>d.y+rectwidth)
                    .style("dominant-baseline","text-before-edge")
                    .text(d=>d.name)
                    );
   
        
}

const giveEvent = _=>{
    d3.select("body").on("click",_=>{
        d3.selectAll(".selected").classed("selected",false);
    })
    d3.select(".delete").on("click",_=>{
       
        d3.selectAll("path.selected").remove();
    })
}

init();
draw();
giveEvent();
<button class="delete">删除连线</button>
<svg id="svgContainer" width="750" height="800"  />
#svg{
    background: #ccc;
}
.link{
    stroke:#000;
}
path.selected{
    stroke:#09c;
}

本项目引用的自定义外部资源