D3用一条线连接来自不同组的矩形

d3 connecting rectangles from different groups by a line

本文关键字:一条 连接 D3      更新时间:2023-09-26

我正在尝试连接来自不同组的两个矩形,但是组可以拖动。如果群是静态的,就能做功。但是当我拖动大的组时,线条的行为就变得混乱了。我想点击一个圆圈并开始绘制线条,当鼠标移动时移动线条,然后我想点击另一个圆圈并结束线条。这是我干活用的小提琴,有人能帮我一把吗?

<g id="a" transform="translate(0,0)">
    <g>
        <rect x="10" y="10" width="200" height="200" fill="red"></rect>
        <circle r="5" cx="10" cy="105" fill="blue"></circle>
        <circle r="5" cx="210" cy="105" fill="blue"></circle>
        <line id="lineOne" x1="210" y1="105" x2="200" y2="200" style="stroke: green; stroke-width: 3px"></line>
    </g>
    <g id="b" class="e">
        <rect x="20" y="20" width="50" height="50" fill="black"></rect>
        <circle r="5" cx="20" cy="45" fill="blue"></circle>
        <circle r="5" cx="70" cy="45" fill="blue"></circle>
    </g>
    <g id="c" class="e">
        <rect x="90" y="20" width="50" height="50" fill="black"></rect>
        <circle r="5" cx="90" cy="45" fill="blue"></circle>
        <circle r="5" cx="140" cy="45" fill="blue"></circle>
    </g>
</g>

<script>
    d3.select('svg').on('mousemove', function () {
        d3.select('#lineOne').attr('x2', d3.mouse(this)[0]).attr('y2', d3.mouse(this)[1]);
    })
    ;
    d3.select('#a').call(d3.behavior.drag()
            .origin(function () {
                var t = d3.select(this);
                return {
                    x: t.attr("x") + d3.transform(t.attr("transform")).translate[0],
                    y: t.attr("y") + d3.transform(t.attr("transform")).translate[1]
                };
            })
            .on('drag', function () {
                d3.select(this).attr('transform', 'translate(' + d3.event.x + ',' + d3.event.y + ')');
            }));
    d3.selectAll('.e')
            .call(d3.behavior.drag()
                    .origin(function () {
                        var t = d3.select(this);
                        return {
                            x: t.attr("x") + d3.transform(t.attr("transform")).translate[0],
                            y: t.attr("y") + d3.transform(t.attr("transform")).translate[1]
                        };
                    }
            )
                    .on('dragstart', function () {
                        d3.event.sourceEvent.stopPropagation();
                    })
                    .on('drag', function () {
                        var parent = d3.select('#a').select('rect');
                        var current = d3.select(this).select('rect');
                        var dx = d3.event.x;
                        var dy = d3.event.y;
                        var x, y;

                        if (((Number(current.attr('x')) + dx) > Number(parent.attr('width')))) {
                            console.log('x case');
                            x = Number(parent.attr('width')) - Number(current.attr('width')) - Number(current.attr('x'));
                        }
                        else if (dx < 0 && (Number(current.attr('x')) - Number(parent.attr('x')) + dx  ) < 0) {
                            x = -1 * Number(current.attr('x')) + 20;
                        }
                        else {
                            x = dx;
                        }

                        if (((Number(current.attr('y')) + dy) > Number(parent.attr('height')))) {
                            y = Number(parent.attr('height')) - Number(current.attr('height')) - Number(current.attr('y'));
                        }
                        else if (dy < 0 && (Number(current.attr('y')) - Number(parent.attr('y')) + dy  ) < 0) {
                            y = -1 * Number(current.attr('y')) + 20;
                        }
                        else {
                            y = dy;
                        }
                        d3.select(this).attr('transform', 'translate(' + x + ',' + y + ')');
                    }))
            .on('dragend', function () {
                d3.select(this).call(d3.behavior.drag().origin(function () {
                    var t = d3.select(this);
                    return {x: t.attr("x"), y: t.attr("y")};
                }))
            })
    ;

</script>

在拖动矩形时也必须更新链接。用变换矩阵计算x1,x2,y1, y2的位置。我已经创建了一个工作代码片段。有关更多细节,请参考代码。如有疑问,请尽管提。

d3.selectAll(".line")[0]
.forEach(function(el) {
    var d = d3.select(el).data()[0],
        line = d3.select(el);
    if (d.src == g.attr("id")) {
        var pt = svg.createSVGPoint();
        pt.x = parseInt(g.select("circle." + d.sPos).attr("cx"));
        pt.y = parseInt(g.select("circle." + d.sPos).attr("cy"));
        pt = pt.matrixTransform(g.node().getCTM());
        pt = pt.matrixTransform(d3.select("#a").node().getCTM().inverse());
        line.attr('x1', pt.x).attr('y1', pt.y);
    } else if (d.tgt == g.attr("id")) {
        var pt = svg.createSVGPoint();
        pt.x = parseInt(g.select("circle." + d.ePos).attr("cx"));
        pt.y = parseInt(g.select("circle." + d.ePos).attr("cy"));
        pt = pt.matrixTransform(g.node().getCTM());
        pt = pt.matrixTransform(d3.select("#a").node().getCTM().inverse());
        line.attr('x2', pt.x)
            .attr('y2', pt.y);
    }
});

var svg = d3.select('svg').node();
d3.select('svg').on('mousemove', function() {
    if (dragging) {
        var rad = 5;
        var x = d3.mouse(d3.select('#a').node())[0],
            y = d3.mouse(d3.select('#a').node())[1];
        d3.select('#dummyPath').attr('x2', x - rad).attr('y2', y - rad);
        d3.select('#dummyPath').style("display", "block")
    }
});
d3.select('#a').call(d3.behavior.drag()
    .origin(function() {
        var t = d3.select(this);
        return {
            x: t.attr("x") + d3.transform(t.attr("transform")).translate[0],
            y: t.attr("y") + d3.transform(t.attr("transform")).translate[1]
        };
    })
    .on('drag', function() {
        d3.select(this).attr('transform', 'translate(' + d3.event.x + ',' + d3.event.y + ')');
    }));
d3.selectAll('.e')
    .call(d3.behavior.drag()
        .origin(function() {
            var t = d3.select(this);
            return {
                x: t.attr("x") + d3.transform(t.attr("transform")).translate[0],
                y: t.attr("y") + d3.transform(t.attr("transform")).translate[1]
            };
        })
        .on('dragstart', function() {
            d3.event.sourceEvent.stopPropagation();
        })
        .on('drag', function() {
            var g = d3.select(this);
            var parent = d3.select('#a').select('rect');
            var current = d3.select(this).select('rect');
            var dx = d3.event.x;
            var dy = d3.event.y;
            var x, y;
            if (((Number(current.attr('x')) + dx) > Number(parent.attr('width')))) {
                console.log('x case');
                x = Number(parent.attr('width')) - Number(current.attr('width')) - Number(current.attr('x'));
            } else if (dx < 0 && (Number(current.attr('x')) - Number(parent.attr('x')) + dx) < 0) {
                x = -1 * Number(current.attr('x')) + 20;
            } else {
                x = dx;
            }
            if (((Number(current.attr('y')) + dy) > Number(parent.attr('height')))) {
                y = Number(parent.attr('height')) - Number(current.attr('height')) - Number(current.attr('y'));
            } else if (dy < 0 && (Number(current.attr('y')) - Number(parent.attr('y')) + dy) < 0) {
                y = -1 * Number(current.attr('y')) + 20;
            } else {
                y = dy;
            }
            
            d3.select(this).attr('transform', 'translate(' + x + ',' + y + ')');           
       d3.selectAll(".line")[0]
                .forEach(function(el) {
                    var d = d3.select(el).data()[0],
                        line = d3.select(el);                                       
                    if (d.src == g.attr("id")) {
                        var pt = svg.createSVGPoint();
                    pt.x = parseInt(g.select("circle." + d.sPos).attr("cx"));
                    pt.y = parseInt(g.select("circle." + d.sPos).attr("cy"));
                         pt = pt.matrixTransform(g.node().getCTM());
                    pt = pt.matrixTransform(d3.select("#a").node().getCTM().inverse());
                        line.attr('x1', pt.x).attr('y1', pt.y);
                    } else if (d.tgt == g.attr("id")) {
                                      var pt = svg.createSVGPoint();
                    pt.x = parseInt(g.select("circle." + d.ePos).attr("cx"));
                    pt.y = parseInt(g.select("circle." + d.ePos).attr("cy"));
                         pt = pt.matrixTransform(g.node().getCTM());
                    pt = pt.matrixTransform(d3.select("#a").node().getCTM().inverse());
                        line.attr('x2', pt.x)
                            .attr('y2', pt.y);
                    }
                });
  }))
    .on('dragend', function() {
        d3.select(this).call(d3.behavior.drag().origin(function() {
            var t = d3.select(this);
            return {
                x: t.attr("x"),
                y: t.attr("y")
            };
        }))
    })
;
var dragging = false;
var drag = d3.behavior.drag()
    .on("dragstart", function() {
        dragging = true;
        d3.event.sourceEvent.stopPropagation();
        var x = d3.mouse(d3.select('#a').node())[0],
            y = d3.mouse(d3.select('#a').node())[1];
        d3.select('#dummyPath').attr('x1', x).attr('y1', y);        
    })
    .on("dragend", function() {
        var sCircle = d3.select(this);
        var eCircle = d3.select(d3.event.sourceEvent.target);
        dragging = false;
        if (d3.event.sourceEvent.target.tagName == "circle" && this != d3.event.sourceEvent.target) {
            var rad = 5;
            var source = d3.select(this.parentNode).attr("id");
            var target = d3.select(d3.event.sourceEvent.target.parentNode).attr("id");
            var x1 = d3.select('#dummyPath').attr('x1'),
                y1 = d3.select('#dummyPath').attr('y1'),
                x2 = d3.mouse(d3.select('#a').node())[0],
                y2 = d3.mouse(d3.select('#a').node())[1];
            d3.select("#a")
                .append("line")
                .datum({
                    src: source,
                    tgt: target,
                    sPos: sCircle.attr("class"),
                    ePos: eCircle.attr("class")
                })
                .attr("class", "line")
                .attr("x1", x1)
                .attr("y1", y1)
                .attr("x2", x2)
                .attr('y2', y2);
        }
        d3.select('#dummyPath').style("display", "none");
    });
d3.selectAll("circle").call(drag);
#dummyPath {  
  stroke: green; 
  stroke-width: 3px;
  display:none;
  stroke-dasharray: 5 7;
}
.line {
  stroke: blue;
  stroke-width: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="500" height="500">
    <g id="a" transform="translate(0,0)">
        <g id="d">
            <rect x="10" y="10" width="200" height="200" fill="red"></rect>
            <circle r="5" class="left" cx="10" cy="105" fill="blue"></circle>
            <circle r="5" class="right" cx="210" cy="105" fill="blue"></circle>
        </g>
        <g id="b" class="e">
            <rect x="20" y="20" width="50" height="50" fill="black"></rect>
            <circle class="left" r="5" cx="20" cy="45" fill="blue"></circle>
            <circle class="right" r="5" cx="70" cy="45" fill="blue"></circle>
        </g>
        <g id="c" class="e">
            <rect x="90" y="20" width="50" height="50" fill="black"></rect>
            <circle r="5" class="left" cx="90" cy="45" fill="blue"></circle>
            <circle r="5" class="right" cx="140" cy="45" fill="blue"></circle>
        </g>       
      <line id="dummyPath" x1="0" y1="0" x2="0" y2="0" class="dummy"></line>
    </g>   
</svg>

更新:

JSFiddle