SOURCE

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
        typeof define === 'function' && define.amd ? define(['exports'], factory) :
            (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3ForceEasy = global.d3ForceEasy || {}));
}(this, (function (exports) {
    'use strict';
    const option = {
        dom: document.getElementsByTagName('body'),
        color: '',
        key: 'id',
        nodes: [],
        links: [],
        icons: [],//[],'circle'
        zoom: true,
        fixed: true,//拖动锁定
        zoomRange: [0.5, 8],
        linkLabel: {
            show: true,
            key: 'relation'
        },
        curvature: 300,//曲率,越小越弯
        text: {
            show: true,
            style: ''
        },
        alpha: 1,//衰减系数,[0,1]之间,越小迭代次数越多,0时迭代不会停止。
        // forceParams:{ //力导向参数
        //     center:{
        //         x:0,
        //         y:0
        //     },//向心力
        //     collide:{
        //         radius:1,
        //         strength:1,//[0,1]碰撞强度
        //     },//碰撞力
        //     link:{
        //         distance:0,
        //         strength:0,
        //         id:'index',//links使用标识
        //     },//弹簧模型
        //     charge:{
        //         strength:-30,//负值相互排斥
        //     },//电荷力模型
        // }
    };

    const defaultIcon = 'M938.666667 512A426.666667 426.666667 0 1 1 512 85.333333a426.666667 426.666667 0 0 1 426.666667 426.666667z';


    function extend(o, n, override) {
        for (let p in n) {
            if (n.hasOwnProperty(p) && (!o.hasOwnProperty(p) || override))
                o[p] = n[p];
        }
    }


    const drag = simulation => {

        function dragstarted(event, d) {


            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
            d3.select(this).classed("fixed", true);
        }

        function dragged(event, d) {
            d.fx = event.x;
            d.fy = event.y;
        }

        function dragended(event, d) {
            if (!event.active) simulation.alphaTarget(0);

            if (option.fixed) {
                d.fx = clamp(event.x, 0, width);
                d.fy = clamp(event.y, 0, height);
            } else {
                d.fx = null
                d.fy = null
            }
        }

        return d3.drag()
            .on("start", dragstarted)
            .on("drag", dragged)
            .on("end", dragended);
    }

    const color = (d, i) => {
        const scale = d3.schemeCategory10;
        return scale[i];
    }

    let simulation, node, link, text, svg, stage_g, currentClick, width, height,zoom;

    function initForce(userOption) {
        extend(option, userOption, true);

        const dom = option.dom;
        height = dom.offsetHeight;
        width = dom.offsetWidth;

        function zoomed(event) {
            const {transform} = event;
            stage_g.attr('transform', `translate(${transform.x},${transform.y}),scale(${transform.k})`)
        }

         zoom = d3.zoom()
            .scaleExtent(option.zoomRange)
            .on("zoom", zoomed);

        simulation = d3.forceSimulation(option.nodes)
            .force("link", d3.forceLink(option.links).id(d => d.id).distance(150))
            .force("charge", d3.forceManyBody().strength(-400))
            .force("center", d3.forceCenter(width / 2, height / 2))
            .force("x", d3.forceX())
            .force("y", d3.forceY())
            .on("tick", ticked);


        svg = d3.select(`#${option.dom.id}`).append('svg')
            .attr('id', 'd3ForceEasyStage')
            .attr("viewBox", [0, 0, width, height])

        if (option.zoom) {
            svg.call(zoom);
        }

        stage_g = svg.append('g').classed('stage-g', true);


        const marker = stage_g.append("marker")
            .attr("id", "resolved")
            .attr("markerUnits", "userSpaceOnUse")
            .attr("viewBox", "0 -5 10 10")//坐标系的区域
            .attr("refX", 20)//箭头坐标
            .attr("refY", 0)
            .attr("markerWidth", 12)//标识的大小
            .attr("markerHeight", 12)
            .attr("orient", "auto")//绘制方向,可设定为:auto(自动确认方向)和 角度值
            .attr("stroke-width", 2)//箭头宽度
            .append("path")
            .attr("d", "M0,-3L10,0L0,3L3,0")//箭头的路径
            .attr('fill', '#8d8a8e');//箭头颜色


        link = stage_g.append("g")
            .attr("stroke", "#999")
            .attr("fill", "none")
            .attr("stroke-width", 1.5)
            .attr("stroke-opacity", 0.6)
            .attr("marker-end", "url(#resolved)")
            .attr("stroke-width", 1)
            .selectAll(".link-g");


        node = stage_g.append("g")
            .selectAll(".node-g");


        function ticked() {
            link.selectAll('path').attr('d', linkArc)

            link.selectAll('.link-label')
                .attr('x', d => linkLabelLoc(d)[0])
                .attr('y', d => linkLabelLoc(d)[1])

            node
                // .attr("cx", d => d.x)
                // .attr("cy", d => d.y);
                .attr("transform", d => `translate(${d.x - 15},${d.y - 15})`);

            // text.attr("x", d => d.x)
            //     .attr("y", d => d.y)
        }

        // simulation.stop();

        return Object.assign(svg.node(), {
            update({nodes, links}) {

                const old = new Map(node.data().map(d => [d.id, d]));
                nodes = nodes.map(d => Object.assign(old.get(d.id) || {}, d));
                links = links.map(d => Object.assign({}, d));

                node = node
                    .data(nodes, d => d.id)
                    .join(enter => enter.append("g")
                        .classed('force-node', true)
                        .classed("fixed", d => d.fx !== undefined)
                        .call(drag(simulation))
                        .call(node => node.append("title").text(d => d.id))
                        .call(node => node.append('circle')
                            .attr("r", 15)
                            .attr('cx', 15)
                            .attr('cy', 15)
                            .attr('fill', '#fff'))
                        .call(node => node.append('path')
                            .attr("d", d => {
                                if (option.icons.length) {
                                    const iconLi = option.icons.find(item => {
                                        return item.type == d.type
                                    });
                                    return iconLi ? iconLi.icon : defaultIcon;
                                } else {
                                    return defaultIcon
                                }

                            })
                            .classed('icon-path', true)
                            .attr("fill", (d, i) => {
                                if (option.color) {
                                    if (typeof option.color == 'string') {
                                        return option.color
                                    } else {
                                        return option.color[i] || option.color.unshift();
                                    }
                                } else {
                                    return color(d, i)
                                }
                            })
                            .attr('transform', 'scale(0.03)'))
                        .call(node => node.append('text')
                            .text(d => d.name)
                            .classed('node-text', true)
                            .classed('hide', !option.text.show)
                            .attr('x',15)
                            .attr('y',40)
                            .attr('style','dominant-baseline:middle;text-anchor:middle')
                        )
                    )
                    .on('click', (e, d) => {
                        currentClickCallBack(e, d);
                        d3.select(this).classed("fixed", false);
                        node.selectAll('circle').classed('selected', item => item == d)
                    })



                link = link
                    .data(links, d => [d.source, d.target])
                    .join("g")
                    .call(link => link.append("path"))
                    .call(link => link.append("text")
                        .text(d => d[option.linkLabel.key] || '')
                        .classed('link-label', 'abc')
                        .classed('hide', !option.linkLabel.show)
                    );

                simulation.nodes(nodes);
                simulation.force("link").links(links);
                simulation.alpha(1).restart().tick();
                ticked(); // render now!
            }
        });

    }

    function linkArc(d) {
        const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y);
        return `
                M${d.source.x},${d.source.y}
                A${r + option.curvature},${r + option.curvature} 0 0,1 ${d.target.x},${d.target.y}
              `;
    }

    function linkLabelLoc(d) {
        const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y);
        let dx = (d.source.x + d.target.x) / 2;
        let dy = (d.source.y + d.target.y) / 2;
        let x,y,l=10;
        if((d.source.x<d.target.x)&&(d.source.y>d.target.y)){
            x = dx-r/l
            y = dy-r/l
        }else if((d.source.x>=d.target.x)&&(d.source.y>=d.target.y)){
            x = dx-r/l
            y = dy+r/l
        }else if((d.source.x>d.target.x)&&(d.source.y<d.target.y)){
            x = dx+r/l
            y = dy+r/l
        }else if((d.source.x<=d.target.x)&&(d.source.y<=d.target.y)){
            x = dx+r/l
            y = dy-r/l
        }

        //性能受限时放弃判断位置使用dx,dy
        // return [dx,dy]
        return [x,y]
    }

    function clamp(x, lo, hi) {
        return x < lo ? lo : x > hi ? hi : x;
    }


    function updateNodes(nodes, links) {

    }

    function toggleName() {
        option.text.show = !option.text.show;
        svg.selectAll('.node-text').classed('hide', !option.text.show)
    }
    function forceToCenter() {
        let identity = d3.zoomIdentity;

        svg.transition().duration(750).call(zoom.transform, identity);
    }


    exports.initForce = initForce;
    exports.updateNodes = updateNodes;
    exports.toggleName = toggleName;
    exports.forceToCenter = forceToCenter;

    //node-link 576585087 dlw author
    window.d3ForceEasy = exports;
    
    Object.defineProperty(exports, '__esModule', {value: true});
})))




console 命令行工具 X clear

                    
>
console