使用 D3(v4) 绘制人物关系图

任务关系图
(function() {
    var drawNodes = function(data, options) {
        options = options || {}
        var width = options.width || 800, height = options.height || 600 // 绘制区域
        var circleSize = options.circleSize || 40 // 图片大小
        var lineLength = options.lineLength || 200 // 连线长度
        var lineWidth = options.lineWidth || 1 // 连线宽度
        var textOffsetY = options.textOffsetY || 0 // 文字距图片上边距
        var fontSize = options.fontSize || 12 // 字体大小
        var fontColor = options.fontColor || '#999' // 文字颜色

        var simulation
        var nodes = data.nodes
        var edges = data.links

        // 初始化 force simulation
        simulation = d3.forceSimulation()
            .force("link", d3.forceLink().id(function(d) {
                return d.id 
            }).distance(lineLength))
            .force("charge", d3.forceManyBody())
            .force("center", d3.forceCenter(width / 2, height / 2))

        // 画布
        var svg = d3.select('body').append('svg')

        simulation.nodes(nodes).on("tick", ticked)
        simulation.force("link").links(edges)

        // 圆形裁剪区域,用于头像裁剪
        var clips = svg.selectAll('clipPath')
            .data(nodes)
            .enter()
            .append('clipPath')
            .attr('id', function(d, i) {return 'avatar-clip_' + i})
            .append('circle')
            .attr('cx', dx())
            .attr('cy', dy())
            .attr('r', circleSize / 2)

        // 画线
        var link = svg.selectAll("line")
            .data(edges)
            .enter()
            .append("line")
            .attr("stroke", fontColor)
            .attr("stroke-width", lineWidth)

        // 画头像
        var node = svg.selectAll('image')
            .data(nodes)
            .enter()
            .append("image")
            .attr("xlink:href", function(d) {return d.symbol})
            .attr("x", dx(-circleSize / 2))
            .attr("y", dy(-circleSize / 2))
            .attr("clip-path", function(d, i) {
                return "url(#avatar-clip_" + i + ")"
            })
            .attr("width", circleSize)
            .attr("height", circleSize)
            .on("click", function(d) { console.log(d) }) // 处理点击事件
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended))

        // 画文字
        var text = svg.selectAll('text')
            .data(nodes)
            .enter()
            .append('text')
            .text(function(d) {return d.name})
            .attr('font-size', fontSize)
            .attr('text-anchor',"middle")
            .attr('fill', fontColor)
            .attr("x", dx())
            .attr("y", dy(circleSize + textOffsetY))

        function dragstarted(d) {
            if (!d3.event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }

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

        function dragended(d) {
            if (!d3.event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
        }

        // 由于力导向图是不断运动的,每一时刻都在发生更新,
        // 因此,必须不断更新节点和连线的位置
        function ticked() {
            if (!link || !node) {
                return
            }

            var offset =  circleSize / 2

            link.attr("x1", function(d) { return d.source.x; })
                .attr("y1", function(d) { return d.source.y; })
                .attr("x2", function(d) { return d.target.x; })
                .attr("y2", function(d) { return d.target.y; })

            node.attr("x", dx(-offset))
                .attr("y", dy(-offset))

            clips.attr("cx", dx()).attr("cy", dy())

            text.attr('x',  dx())
                .attr('y', dy(circleSize + textOffsetY))
        }

        function dx(offset) {
            return function(d) {
                return d.x + (offset || 0)
            }
        }

        function dy(offset) {
            return function(d) {
                return d.y + (offset || 0)
            }
        }
    }

    d3.json('./data.json', function(err, res) {
        if (!err) {
            drawNodes(res)
        } else {
            console.error(err)
        }
    })
})()

h5-demo

Categories jsTags