Javascript d3js变换嵌套组图像

Javascript d3js变换嵌套组图像,javascript,d3.js,svg,Javascript,D3.js,Svg,我正在从事[这个][1]d3项目。基本上,我正在尝试创建一个类似SQL的查询生成器。我可以将框放置到绘图区域以及框内的其他操作员。那我就可以把它们都连接起来了。我试图翻译两个嵌套在组中的图像。我想把小东西放到大盒子里。我可以分别变换大盒子和小操作符。当我尝试首先移动小操作符时,问题就出现了。我想先移动小操作员,然后移动大箱子。同时我想保持小操作员和大箱子的相对位置不变。但是当我在移动一个小盒子后尝试移动大盒子时,它会重置其位置。这是我在中国工作的一个演示 PS:我通过拖动圆圈并放入另一个圆圈来

我正在从事[这个][1]d3项目。基本上,我正在尝试创建一个类似SQL的查询生成器。我可以将框放置到绘图区域以及框内的其他操作员。那我就可以把它们都连接起来了。我试图翻译两个嵌套在组中的图像。我想把小东西放到大盒子里。我可以分别变换大盒子和小操作符。当我尝试首先移动小操作符时,问题就出现了。我想先移动小操作员,然后移动大箱子。同时我想保持小操作员和大箱子的相对位置不变。但是当我在移动一个小盒子后尝试移动大盒子时,它会重置其位置。这是我在中国工作的一个演示

PS:我通过拖动圆圈并放入另一个圆圈来连接两个元素。


有人能指出我的错误吗

根据我开发可视化编辑器的经验,我可以说相对位置(如操作符中的x、y)可以也应该作为数据进行管理

尝试更改该数据(而不是直接更改svg元素的x、y属性),并以d3js方式绑定数据。它将更加地道和快速


PS:我知道模型和视图的分离,我也知道沿模型存储视觉属性的不好的一面,但如果它只是该模型的一个视图,那肯定是最好的方法。

经历了各种各样的麻烦之后,我找到了答案。实际上,这一切都是关于坐标系,以及把东西放在哪里,以及如何组织它。一旦我弄清楚了,答案是显而易见的

<svg width="500" height="500" style="background-color: blue">


    <g id="draw">

        <rect class="container" height="500" width="500" x="0" y="0" style="fill:yellow"></rect>

        <line class="dummyLineOutsideQbox"></line>

        <g class="qbox" id="qbox">

            <line class="dummyLineInsideQbox"></line>


            <image x="10" y="10" class="container" initial-x="10" initial-y="10" xlink:href="images/query.png"
                   width="110"
                   height="110"></image>
            <circle class="left" id="qbox-left" initial-cx="10" initial-cy="65" cx="10" cy="65" r="5"
                    style="fill:red"></circle>
            <circle class="right" id="qbox-right" initial-cx="120" initial-cy="65" cx="120" cy="65" r="5"
                    style="fill:red"></circle>

            <g id="op1" class="op">
                <image class="opim" x="10" y="10" class="container" initial-x="10" initial-y="10"
                       xlink:href="images/filter.png" width="50"
                       height="50"></image>
                <circle id="op1-left" class="left" initial-cx="10" initial-cy="35" cx="10" cy="35" r="5"
                        style="fill:red"></circle>
                <circle id="op1-right" class="right" initial-cx="60" initial-cy="35" cx="60" cy="35" r="5"
                        style="fill:red"></circle>
            </g>

            <g id="op2" class="op">
                <image class="opim" x="60" y="60" initial-x="60" initial-y="60"
                       xlink:href="images/filter.png" width="50"
                       height="50"></image>
                <circle id="op2-left" class="left" initial-cx="60" initial-cy="85" cx="60" cy="85" r="5"
                        style="fill:red"></circle>
                <circle id="op2-right" class="right" initial-cx="110" initial-cy="85" cx="110" cy="85" r="5"
                        style="fill:red"></circle>
            </g>

        </g>

        <g class="qbox" id="qbox2">

            <line class="dummyLineInsideQbox"></line>

            <image x="110" y="110" class="container" initial-x="110" initial-y="110" xlink:href="images/query.png"
                   width="110"
                   height="110"></image>
            <circle class="left" id="qbox2-left" initial-cx="110" initial-cy="165" cx="110" cy="165" r="5"
                    style="fill:red"></circle>
            <circle class="right" id="qbox2-right" initial-cx="220" initial-cy="265" cx="220" cy="165" r="5"
                    style="fill:red"></circle>

            <g id="op3" class="op">
                <image class="opim" x="110" y="110" class="container" initial-x="110" initial-y="110"
                       xlink:href="images/filter.png" width="50"
                       height="50"></image>
                <circle id="op3-left" class="left" initial-cx="110" initial-cy="135" cx="110" cy="135" r="5"
                        style="fill:red"></circle>
                <circle id="op3-right" class="right" initial-cx="160" initial-cy="135" cx="160" cy="135" r="5"
                        style="fill:red"></circle>
            </g>
            <g id="op4" class="op">
                <image class="opim" x="160" y="160" initial-x="160" initial-y="160"
                       xlink:href="images/filter.png" width="50"
                       height="50"></image>
                <circle id="op4-left" class="left" initial-cx="160" initial-cy="185" cx="160" cy="185" r="5"
                        style="fill:red"></circle>
                <circle id="op4-right" class="right" initial-cx="210" initial-cy="185" cx="210" cy="185" r="5"
                        style="fill:red"></circle>
            </g>


        </g>


    </g>

</svg>

<script>
    var qBox = d3.selectAll('.qbox')
            .on('dblclick', function () {
                var g = d3.select(this);
                var scale = 'scale(1.2,1.2)';
                g.attr('transform', g.attr('transform') + ' ' + scale);
            });
    var opBox = d3.selectAll('.op');
    var circles = d3.selectAll('circle');


    var cDrag = d3.behavior.drag()

                    .on('dragstart', function () {
                        d3.event.sourceEvent.stopPropagation();
                    })
                    .on('drag', function () {

                        var thisCircle = d3.select(this);
                        var thisGroup = thisCircle.select(function () {
                            return this.parentNode;
                        });
                        var thisGroupTransform = d3.transform(thisGroup.attr('transform')).translate;

                        var thisGroupParent = d3.select(this).select(function () {
                            return this.parentNode;
                        }).select(function () {
                            return this.parentNode;
                        });

                        var thisGroupParentId = thisGroupParent.attr('id');
                        var thisGroupParentClass = thisGroupParent.attr('class');


                        if (thisGroupParentClass == 'qbox') {
                            dummyLine = d3.select('#' + thisGroupParentId).select('.dummyLineInsideQbox');
                        }
                        else {
                            dummyLine = d3.select('#' + thisGroupParentId).select('.dummyLineOutsideQbox');

                        }

                        console.log('dummyLine ', dummyLine.attr('class'));


                        dummyLine
                                .style('visibility', 'visible')
                                .style('stroke', 'red')
                                .style('stroke-width', '3px')
                                .attr('x1', Number(thisCircle.attr('cx')) + thisGroupTransform[0])
                                .attr('real-x1', thisCircle.attr('cx'))
                                .attr('y1', Number(thisCircle.attr('cy')) + thisGroupTransform[1])
                                .attr('real-y1', thisCircle.attr('cy'))
                                .attr('x2', d3.mouse(this)[0] + thisGroupTransform[0])
                                .attr('y2', d3.mouse(this)[1] + thisGroupTransform[1])
                                .attr('startGroup', thisGroup.attr('id'))
                                .attr('startCircleClass', thisCircle.attr('class'))
                        ;


                    })
                    .on('dragend', function () {


                        var thisCircle = d3.select('#' + circleID);
                        var thisCircleClass = thisCircle.attr('class');
                        var thisGroup = thisCircle.select(function () {
                            return this.parentNode;
                        });

                        var thisGroupTransform = d3.transform(thisGroup.attr('transform')).translate;

                        var thisGroupParent = d3.select(this).select(function () {
                            return this.parentNode;
                        }).select(function () {
                            return this.parentNode;
                        });

                        var thisGroupParentId = thisGroupParent.attr('id');
                        var thisGroupParentClass = thisGroupParent.attr('class');


                        var sourceCircleClass = dummyLine.attr('startCircleClass');
                        var sourceGroup = d3.select('#' + dummyLine.attr('startGroup'));

                        console.log('SOURCE GROUP :', sourceGroup.attr('id'));

                        var targetLineGroup;
                        var lineClass;

                        var t = [0, 0];
                        var x1, x2, y1, y2;

                        if (( sourceGroup.attr('class') == 'op') || (thisGroup.attr('class') == 'op')) {

                            if (sourceGroup.attr('class') == 'op') {
                                targetLineGroup = sourceGroup.select(function () {
                                    return this.parentNode;
                                });
//                                console.log('I am on line 185');
                            } else {
                                targetLineGroup = thisGroup.select(function () {
                                    return this.parentNode;
                                });
//                                console.log('I am on line 190');

                            }
                            lineClass = 'in';
                            x1 = startCircle.attr('cx');
                            y1 = startCircle.attr('cy');
                            x2 = thisCircle.attr('cx');
                            y2 = thisCircle.attr('cy');

                        }

                        if ((sourceGroup.attr('class') == 'qbox') && (thisGroup.attr('class') == 'qbox')) {

                            targetLineGroup = sourceGroup.select(function () {
                                return this.parentNode;
                            });
                            lineClass = 'out';

                            x1 = dummyLine.attr('x1');
                            x2 = dummyLine.attr('x2');
                            y1 = dummyLine.attr('y1');
                            y2 = dummyLine.attr('y2');
                        }

                        targetLineGroup
                                .append('line')
                                .attr('class', lineClass)
                                .attr('id', function () {
                                    return sourceCircleClass + '--' + sourceGroup.attr('id') + '__' + thisCircleClass + '--' + thisGroup.attr('id');
                                })
                                .attr('x1', dummyLine.attr('x1'))
                                .attr('real-x1', dummyLine.attr('x1'))
                                .attr('y1', dummyLine.attr('y1'))
                                .attr('real-y1', dummyLine.attr('y1'))
                                .attr('x2', dummyLine.attr('x2'))
                                .attr('real-x2', dummyLine.attr('x2'))
                                .attr('y2', dummyLine.attr('y2'))
                                .attr('real-y2', dummyLine.attr('y2'))
                                .attr('startGroup', dummyLine.attr('startGroup'))
                                .attr('endGroup', thisGroup.attr('id'))
                                .style('stroke', 'green')
                                .style('stroke-width', '3px')

                        ;
                        dummyLine.style('visibility', 'hidden');


                        console.log('DRAWING LINE ON : ', targetLineGroup.attr('id'))

                    })
            ;

    var svg = d3.select('svg').node();

    var drag = d3.behavior.drag()
                    .origin(function () {
                        var t = d3.transform(d3.select(this).attr("transform")).translate;
                        return {x: t[0], y: t[1]};
                    }).on('dragstart', function () {
                        d3.event.sourceEvent.stopPropagation();
                    }).on('drag', function () {

                        var g = d3.select(this);

                        var mouse = {dx: d3.event.x, dy: d3.event.y};
                        var currentObj = {
                            x: g.select('image').attr('x'),
                            y: g.select('image').attr('y'),
                            width: g.select('image').attr('width'),
                            height: g.select('image').attr('height')
                        };
                        var parentObj = {
                            x: (Number(g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('x'))), // + Number(d3.transform(parent.attr('transform')).translate[0])),
                            y: (Number(g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('y'))), // + Number(d3.transform(parent.attr('transform')).translate[1])),
                            width: g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('width'),
                            height: g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('height')
                        };


                        var loc = getXY(mouse, currentObj, parentObj);
                        d3.select(this).attr('transform', 'translate(' + loc.x + ',' + loc.y + ')');
//                        d3.select(this).attr('transform', 'translate(' + d3.event.x + ',' + d3.event.y + ')');

                        var thisGroupId = d3.select(this).attr('id');
                        var groupClass = d3.select(this).attr('class');

                        var thisGroupClass = d3.select(this).attr('class');
                        var tLineClass;
                        if (thisGroupClass == 'qbox') {
                            tLineClass = 'out'
                        } else {
                            tLineClass = 'in'
                        }

                        d3.selectAll('line.' + tLineClass)[0].forEach(function (e1) {

                            var line = d3.select(e1);

                            var lineId = line.attr('id');




                            var lineStartGroup = lineId.split('__')[0].split('--')[1];
                            var lineStartSide = lineId.split('__')[0].split('--')[0];
                            var lineEndGroup = lineId.split('__')[1].split('--')[1];
                            var lineEndSide = lineId.split('__')[1].split('--')[0];

                            console.log('-------------------');
                            console.log(lineId);
                            console.log(lineStartGroup);
                            console.log(lineEndGroup);
                            console.log('-------------------');

                            var c;
                            var ctm;

                            if (thisGroupId == lineStartGroup) {
                                var t = d3.transform(d3.select('#' + thisGroupId).attr('transform')).translate;

                                if (lineStartSide == 'left') {
                                    c = d3.select('#' + lineStartGroup).select('.left');
                                    ctm = c.node().getCTM();
                                } else {
                                    c = d3.select('#' + lineStartGroup).select('.right');
                                    ctm = c.node().getCTM();
                                }

                                var x = d3.transform(c.select(function () {
                                    return this.parentNode;
                                }).attr('transform')).translate;


                                console.log('START e'+ctm['e']);
                                console.log('START ex'+x[0]);
                                console.log('START f'+ctm['f']);
                                console.log('START fx'+x[0]);
                                line.attr('x1', Number(c.attr('cx')) + Number(x[0]));
                                line.attr('y1', Number(c.attr('cy')) + Number(x[1]));
                            }

                            if (thisGroupId == lineEndGroup) {
//                                var t = d3.transform(d3.select('#' + thisGroupId).attr('transform')).translate;
//
//                                line.attr('x2', Number(line.attr('real-x2')) + Number(t[0]));
//                                line.attr('y2', Number(line.attr('real-y2')) + Number(t[1]));

                                if (lineEndSide == 'left') {
                                    c = d3.select('#' + lineEndGroup).select('.left');

                                } else {
                                    c = d3.select('#' + lineEndGroup).select('.right');

                                }

                                var x = d3.transform(c.select(function () {
                                    return this.parentNode;
                                }).attr('transform')).translate;

                                ctm = c.node().getCTM();
                                console.log('END circleId'+c.attr('id'));

                                line.attr('x2', Number(c.attr('cx')) + Number(x[0]));
                                line.attr('y2', Number(c.attr('cy')) + Number(x[1]));

                            }


                        });


                    })

            ;

    opBox.call(drag);
    qBox.call(drag);
    circles.call(cDrag);

    var circleID;

    var dummyLine;

    circles.on('mouseover', function () {
        circleID = d3.select(this).attr('id');
    }).on('mouseout', function () {
        circleID = null;
    })

function getXY(mouse, current, parent) {

    var obj = {
        x: 0,
        y: 0
    };

    var dx = mouse.dx;
    var dy = mouse.dy;

    obj.x = dx;
    obj.y = dy;

    var xGap = current.x - parent.x;
    var yGap = current.y - parent.y;

    if (dx < 0) {
        if ((dx + xGap) < 0) {
            obj.x = -1 * xGap + 10;
        }
    } else {
        if ((dx + xGap) > parent.width) {
            obj.x = parent.width - xGap - current.width - 10;
        }
    }

    if (dy < 0) {
        if ((dy + yGap) < 0) {
            obj.y = -1 * yGap + 10;
        }

    } else {
        if ((dy + yGap) > parent.height) {
            obj.y = parent.height - yGap - current.height - 10;
        }
    }


    return obj;
}

</script>

var qBox=d3.selectAll(“.qBox”)
.on('dblclick',函数(){
var g=d3。选择(本);
var标度='标度(1.2,1.2)';
g、 attr('transform',g.attr('transform')+''+scale);
});
var opBox=d3.selectAll('.op');
var circles=d3。选择all('circle');
var cDrag=d3.behavior.drag()
.on('dragstart',函数(){
d3.event.sourceEvent.stopPropagation();
})
.on('drag',function(){
var thisCircle=d3。选择(此);
var thisGroup=thiscorcle.select(函数(){
返回此.parentNode;
});
var thisGroupTransform=d3.transform(thisGroup.attr('transform')).translate;
var thisGroupParent=d3.select(this).select(函数(){
返回此.parentNode;
}).选择(函数(){
返回此.parentNode;
});
var thisGroupParentId=thisGroupParent.attr('id');
var thisGroupParentClass=thisGroupParent.attr('class');
if(thisGroupParentClass=='qbox'){
dummyLine=d3.select('#'+thisGroupParentId.select('.dummylineInsideeQbox');
}
否则{
dummyLine=d3.select('#'+thisGroupParentId)。select('.dummyLineOutsideQbox');
}
console.log('dummyLine',dummyLine.attr('class');
杜米林
.style('可见性','可见')
.style('笔划','红色')
.style('stroke-width','3px')
.attr('x1',数字(thisCircle.attr('cx'))+thisGroupTransform[0])
.attr('real-x1',thisCircle.attr('cx'))
.attr('y1',Number(thiscirle.attr('cy'))+thisGroupTransform[1])
.attr('real-y1',thiscorcle.attr('cy'))
.attr('x2',d3.鼠标(this)[0]+thisGroupTransform[0])
.attr('y2',d3.mouse(this)[1]+thisGroupTransform[1])
.attr('startGroup',thisGroup.attr('id'))
.attr('startCircleClass',thisCircle.attr('class'))
;
})
.on('dragend',函数(){
var thiscille=d3。选择(“#”+circleID);
var thiscirleclass=thiscirle.attr('class');
var thisGroup=thiscorcle.select(函数(){
返回此.parentNode;
});
var thisGroupTransform=d3.transform(thisGroup.attr('transform')).translate;
var thisGroupParent=d3.select(this).select(函数(){
返回此.parentNode;
}).选择(函数(){
返回此.parentNode;
});
var thisGroupParentId=thisGroupParent.attr('id');
var thisGroupParentClass=thisGroupParent.attr('class');
var sourceCircleClass=dummyLine.attr('startCircleClass');
var sourceGroup=d3.select(“#”+dummyLine.attr('startGroup');
console.log('sourceGroup:',sourceGroup.attr('id');
var目标线性群;
var线性类;
弗吉尼亚州
    var qBox = d3.selectAll('.qbox')
            .on('dblclick', function () {
                var g = d3.select(this);
                var scale = 'scale(1.2,1.2)';
                g.attr('transform', g.attr('transform') + ' ' + scale);
            });
    var opBox = d3.selectAll('.op');
    var circles = d3.selectAll('circle');


    var cDrag = d3.behavior.drag()

                    .on('dragstart', function () {
                        d3.event.sourceEvent.stopPropagation();
                    })
                    .on('drag', function () {

                        var dummyLine = d3.select('#dummyLine');
                        var me = d3.select(this);

                        var transForm = me.node().getCTM();
                        var t2 = me.select(function () {
                            return this.parentNode;
                        }).select(function () {
                            return this.parentNode;
                        }).select('circle').node().getCTM();

                        var tC = d3.transform(d3.select(this).attr('transform')).translate;
                        var tP = d3.transform(d3.select(this).select(function () {
                            return this.parentNode;
                        }).attr('transform')).translate;

                        console.log(transForm);

                        var meX = t2['e'];
                        var meY = t2['f'];


                        dummyLine
                                .style('visibility', 'visible')
                                .attr('tx1', Number(me.attr('cx')))
                                .attr('x1', Number(me.attr('cx')) + (Number(transForm['e'] - Number(meX))))
                                .attr('ty1', Number(me.attr('cy')))
                                .attr('y1', Number(me.attr('cy')) + (Number(transForm['f'] - Number(meY))))
                                .attr('x2', Number(d3.event.x) )
                                .attr('tx2', Number(d3.event.x) + Number(tP[0]) - Number(tC[0]))
                                .attr('y2', Number(d3.event.y) )
                                .attr('ty2', Number(d3.event.y) + Number(tP[1]) - Number(tC[0]))
                                .attr('start', me.attr('id'))

                        ;

                    })
                    .on('dragend', function () {

                        var g = d3.select(this).select(function () {
                            return this.parentNode;
                        }).select(function () {
                            return this.parentNode;
                        });


                        var dummyLine = d3.select('#dummyLine');

                        dummyLine.style('visibility', 'hidden');

                        d3.select('.qbox')
                                .append('line')
                                .attr('id', function () {
                                    return dummyLine.attr('start') + '__' + circleID;
                                })

                                .attr('x1', dummyLine.attr('x1'))
                                .attr('ix1', dummyLine.attr('tx1'))
                                .attr('x2', dummyLine.attr('x2'))
                                .attr('ix2', d3.select('#' + circleID).attr('cx'))
                                .attr('y1', dummyLine.attr('y1'))
                                .attr('iy1', dummyLine.attr('ty1'))
                                .attr('y2', dummyLine.attr('y2'))
                                .attr('iy2', d3.select('#' + circleID).attr('cy'))
                                .attr('start', dummyLine.attr('start'))
                                .attr('end', circleID)
                                .style('stroke', 'green')
                                .style('stroke-width', '2px')

                        ;
                    })
            ;

    var svg = d3.select('svg').node();

    var drag = d3.behavior.drag()
                    .origin(function () {
                        var t = d3.transform(d3.select(this).attr("transform")).translate;
                        return {x: t[0], y: t[1]};
                    }).on('dragstart', function () {
                        d3.event.sourceEvent.stopPropagation();
                    }).on('drag', function () {

                        var g = d3.select(this);

                        var mouse = {dx: d3.event.x, dy: d3.event.y};
                        var currentObj = {
                            x: g.select('image').attr('x'),
                            y: g.select('image').attr('y'),
                            width: g.select('image').attr('width'),
                            height: g.select('image').attr('height')
                        };
                        var parentObj = {
                            x: (Number(g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('x'))), // + Number(d3.transform(parent.attr('transform')).translate[0])),
                            y: (Number(g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('y'))), // + Number(d3.transform(parent.attr('transform')).translate[1])),
                            width: g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('width'),
                            height: g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('height')
                        };


                        var loc = getXY(mouse, currentObj, parentObj);
                        d3.select(this).attr('transform', 'translate(' + loc.x + ',' + loc.y + ')');
//                        d3.select(this).attr('transform', 'translate(' + d3.event.x + ',' + d3.event.y + ')');

                        var groupId = d3.select(this).attr('id');
                        var groupClass = d3.select(this).attr('class');

                        d3.selectAll('line')[0].forEach(function (e1) {

                            var line = d3.select(e1);
//                            console.log('groupId: ', groupId);


                            if (line.attr('id') != 'dummyLine' && groupClass != 'qbox') {

//                                console.log('--------------');
//                                console.log('lineId: ', line.attr('id'));

                                var lineStart = line.attr('start').split('-')[0];
                                var lineEnd = line.attr('end').split('-')[0];
//                                console.log('lineStatr : ', lineStart);
//                                console.log('lineEnd : ', lineEnd);

                                var t = d3.transform(d3.select('#' + groupId).attr('transform')).translate;
                                var t2 = d3.transform(d3.select('#' + groupId).select(function () {
                                    return this.parentNode;
                                }).attr('transform')).translate;

                                console.log('groupID ', groupId);

                                if (lineStart == groupId) {
                                    var t = d3.transform(d3.select('#' + lineStart).attr('transform')).translate;

                                    line.attr('x1', Number(line.attr('ix1')) + (Number(t[0])));
                                    line.attr('y1', Number(line.attr('iy1')) + Number(t[1]));
//                                    line.attr('x1', Number(line.attr('ix1')) - (-Number(t[0])+Number(t2[0])));
//                                    line.attr('y1', Number(line.attr('iy1')) - (-Number(t[1]+Number(t2[1]))));

                                }

                                if (lineEnd == groupId) {
                                    var t = d3.transform(d3.select('#' + lineEnd).attr('transform')).translate;

                                    line.attr('x2', Number(line.attr('ix2')) + Number(t[0]));
                                    line.attr('y2', Number(line.attr('iy2')) + Number(t[1]));
//                                    line.attr('x2', Number(line.attr('ix2')) - Number(t[0]));
//                                    line.attr('y2', Number(line.attr('iy2')) - Number(t[1]));
//                                    line.attr('x2', Number(line.attr('ix2')) - (Number(t[0]+Number(t2[0]))));
//                                    line.attr('y2', Number(line.attr('iy2')) - (Number(t[1]+Number(t2[1]))));

                                }

                            }


                        });


                    })

            ;

    opBox.call(drag);
    qBox.call(drag);
    circles.call(cDrag);

    var circleID;

    circles.on('mouseover', function () {
        circleID = d3.select(this).attr('id');
    }).on('mouseout', function () {
        circleID = null;
    })
<svg width="500" height="500" style="background-color: blue">


    <g id="draw">

        <rect class="container" height="500" width="500" x="0" y="0" style="fill:yellow"></rect>

        <line class="dummyLineOutsideQbox"></line>

        <g class="qbox" id="qbox">

            <line class="dummyLineInsideQbox"></line>


            <image x="10" y="10" class="container" initial-x="10" initial-y="10" xlink:href="images/query.png"
                   width="110"
                   height="110"></image>
            <circle class="left" id="qbox-left" initial-cx="10" initial-cy="65" cx="10" cy="65" r="5"
                    style="fill:red"></circle>
            <circle class="right" id="qbox-right" initial-cx="120" initial-cy="65" cx="120" cy="65" r="5"
                    style="fill:red"></circle>

            <g id="op1" class="op">
                <image class="opim" x="10" y="10" class="container" initial-x="10" initial-y="10"
                       xlink:href="images/filter.png" width="50"
                       height="50"></image>
                <circle id="op1-left" class="left" initial-cx="10" initial-cy="35" cx="10" cy="35" r="5"
                        style="fill:red"></circle>
                <circle id="op1-right" class="right" initial-cx="60" initial-cy="35" cx="60" cy="35" r="5"
                        style="fill:red"></circle>
            </g>

            <g id="op2" class="op">
                <image class="opim" x="60" y="60" initial-x="60" initial-y="60"
                       xlink:href="images/filter.png" width="50"
                       height="50"></image>
                <circle id="op2-left" class="left" initial-cx="60" initial-cy="85" cx="60" cy="85" r="5"
                        style="fill:red"></circle>
                <circle id="op2-right" class="right" initial-cx="110" initial-cy="85" cx="110" cy="85" r="5"
                        style="fill:red"></circle>
            </g>

        </g>

        <g class="qbox" id="qbox2">

            <line class="dummyLineInsideQbox"></line>

            <image x="110" y="110" class="container" initial-x="110" initial-y="110" xlink:href="images/query.png"
                   width="110"
                   height="110"></image>
            <circle class="left" id="qbox2-left" initial-cx="110" initial-cy="165" cx="110" cy="165" r="5"
                    style="fill:red"></circle>
            <circle class="right" id="qbox2-right" initial-cx="220" initial-cy="265" cx="220" cy="165" r="5"
                    style="fill:red"></circle>

            <g id="op3" class="op">
                <image class="opim" x="110" y="110" class="container" initial-x="110" initial-y="110"
                       xlink:href="images/filter.png" width="50"
                       height="50"></image>
                <circle id="op3-left" class="left" initial-cx="110" initial-cy="135" cx="110" cy="135" r="5"
                        style="fill:red"></circle>
                <circle id="op3-right" class="right" initial-cx="160" initial-cy="135" cx="160" cy="135" r="5"
                        style="fill:red"></circle>
            </g>
            <g id="op4" class="op">
                <image class="opim" x="160" y="160" initial-x="160" initial-y="160"
                       xlink:href="images/filter.png" width="50"
                       height="50"></image>
                <circle id="op4-left" class="left" initial-cx="160" initial-cy="185" cx="160" cy="185" r="5"
                        style="fill:red"></circle>
                <circle id="op4-right" class="right" initial-cx="210" initial-cy="185" cx="210" cy="185" r="5"
                        style="fill:red"></circle>
            </g>


        </g>


    </g>

</svg>

<script>
    var qBox = d3.selectAll('.qbox')
            .on('dblclick', function () {
                var g = d3.select(this);
                var scale = 'scale(1.2,1.2)';
                g.attr('transform', g.attr('transform') + ' ' + scale);
            });
    var opBox = d3.selectAll('.op');
    var circles = d3.selectAll('circle');


    var cDrag = d3.behavior.drag()

                    .on('dragstart', function () {
                        d3.event.sourceEvent.stopPropagation();
                    })
                    .on('drag', function () {

                        var thisCircle = d3.select(this);
                        var thisGroup = thisCircle.select(function () {
                            return this.parentNode;
                        });
                        var thisGroupTransform = d3.transform(thisGroup.attr('transform')).translate;

                        var thisGroupParent = d3.select(this).select(function () {
                            return this.parentNode;
                        }).select(function () {
                            return this.parentNode;
                        });

                        var thisGroupParentId = thisGroupParent.attr('id');
                        var thisGroupParentClass = thisGroupParent.attr('class');


                        if (thisGroupParentClass == 'qbox') {
                            dummyLine = d3.select('#' + thisGroupParentId).select('.dummyLineInsideQbox');
                        }
                        else {
                            dummyLine = d3.select('#' + thisGroupParentId).select('.dummyLineOutsideQbox');

                        }

                        console.log('dummyLine ', dummyLine.attr('class'));


                        dummyLine
                                .style('visibility', 'visible')
                                .style('stroke', 'red')
                                .style('stroke-width', '3px')
                                .attr('x1', Number(thisCircle.attr('cx')) + thisGroupTransform[0])
                                .attr('real-x1', thisCircle.attr('cx'))
                                .attr('y1', Number(thisCircle.attr('cy')) + thisGroupTransform[1])
                                .attr('real-y1', thisCircle.attr('cy'))
                                .attr('x2', d3.mouse(this)[0] + thisGroupTransform[0])
                                .attr('y2', d3.mouse(this)[1] + thisGroupTransform[1])
                                .attr('startGroup', thisGroup.attr('id'))
                                .attr('startCircleClass', thisCircle.attr('class'))
                        ;


                    })
                    .on('dragend', function () {


                        var thisCircle = d3.select('#' + circleID);
                        var thisCircleClass = thisCircle.attr('class');
                        var thisGroup = thisCircle.select(function () {
                            return this.parentNode;
                        });

                        var thisGroupTransform = d3.transform(thisGroup.attr('transform')).translate;

                        var thisGroupParent = d3.select(this).select(function () {
                            return this.parentNode;
                        }).select(function () {
                            return this.parentNode;
                        });

                        var thisGroupParentId = thisGroupParent.attr('id');
                        var thisGroupParentClass = thisGroupParent.attr('class');


                        var sourceCircleClass = dummyLine.attr('startCircleClass');
                        var sourceGroup = d3.select('#' + dummyLine.attr('startGroup'));

                        console.log('SOURCE GROUP :', sourceGroup.attr('id'));

                        var targetLineGroup;
                        var lineClass;

                        var t = [0, 0];
                        var x1, x2, y1, y2;

                        if (( sourceGroup.attr('class') == 'op') || (thisGroup.attr('class') == 'op')) {

                            if (sourceGroup.attr('class') == 'op') {
                                targetLineGroup = sourceGroup.select(function () {
                                    return this.parentNode;
                                });
//                                console.log('I am on line 185');
                            } else {
                                targetLineGroup = thisGroup.select(function () {
                                    return this.parentNode;
                                });
//                                console.log('I am on line 190');

                            }
                            lineClass = 'in';
                            x1 = startCircle.attr('cx');
                            y1 = startCircle.attr('cy');
                            x2 = thisCircle.attr('cx');
                            y2 = thisCircle.attr('cy');

                        }

                        if ((sourceGroup.attr('class') == 'qbox') && (thisGroup.attr('class') == 'qbox')) {

                            targetLineGroup = sourceGroup.select(function () {
                                return this.parentNode;
                            });
                            lineClass = 'out';

                            x1 = dummyLine.attr('x1');
                            x2 = dummyLine.attr('x2');
                            y1 = dummyLine.attr('y1');
                            y2 = dummyLine.attr('y2');
                        }

                        targetLineGroup
                                .append('line')
                                .attr('class', lineClass)
                                .attr('id', function () {
                                    return sourceCircleClass + '--' + sourceGroup.attr('id') + '__' + thisCircleClass + '--' + thisGroup.attr('id');
                                })
                                .attr('x1', dummyLine.attr('x1'))
                                .attr('real-x1', dummyLine.attr('x1'))
                                .attr('y1', dummyLine.attr('y1'))
                                .attr('real-y1', dummyLine.attr('y1'))
                                .attr('x2', dummyLine.attr('x2'))
                                .attr('real-x2', dummyLine.attr('x2'))
                                .attr('y2', dummyLine.attr('y2'))
                                .attr('real-y2', dummyLine.attr('y2'))
                                .attr('startGroup', dummyLine.attr('startGroup'))
                                .attr('endGroup', thisGroup.attr('id'))
                                .style('stroke', 'green')
                                .style('stroke-width', '3px')

                        ;
                        dummyLine.style('visibility', 'hidden');


                        console.log('DRAWING LINE ON : ', targetLineGroup.attr('id'))

                    })
            ;

    var svg = d3.select('svg').node();

    var drag = d3.behavior.drag()
                    .origin(function () {
                        var t = d3.transform(d3.select(this).attr("transform")).translate;
                        return {x: t[0], y: t[1]};
                    }).on('dragstart', function () {
                        d3.event.sourceEvent.stopPropagation();
                    }).on('drag', function () {

                        var g = d3.select(this);

                        var mouse = {dx: d3.event.x, dy: d3.event.y};
                        var currentObj = {
                            x: g.select('image').attr('x'),
                            y: g.select('image').attr('y'),
                            width: g.select('image').attr('width'),
                            height: g.select('image').attr('height')
                        };
                        var parentObj = {
                            x: (Number(g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('x'))), // + Number(d3.transform(parent.attr('transform')).translate[0])),
                            y: (Number(g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('y'))), // + Number(d3.transform(parent.attr('transform')).translate[1])),
                            width: g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('width'),
                            height: g.select(function () {
                                return this.parentNode;
                            }).select('.container').attr('height')
                        };


                        var loc = getXY(mouse, currentObj, parentObj);
                        d3.select(this).attr('transform', 'translate(' + loc.x + ',' + loc.y + ')');
//                        d3.select(this).attr('transform', 'translate(' + d3.event.x + ',' + d3.event.y + ')');

                        var thisGroupId = d3.select(this).attr('id');
                        var groupClass = d3.select(this).attr('class');

                        var thisGroupClass = d3.select(this).attr('class');
                        var tLineClass;
                        if (thisGroupClass == 'qbox') {
                            tLineClass = 'out'
                        } else {
                            tLineClass = 'in'
                        }

                        d3.selectAll('line.' + tLineClass)[0].forEach(function (e1) {

                            var line = d3.select(e1);

                            var lineId = line.attr('id');




                            var lineStartGroup = lineId.split('__')[0].split('--')[1];
                            var lineStartSide = lineId.split('__')[0].split('--')[0];
                            var lineEndGroup = lineId.split('__')[1].split('--')[1];
                            var lineEndSide = lineId.split('__')[1].split('--')[0];

                            console.log('-------------------');
                            console.log(lineId);
                            console.log(lineStartGroup);
                            console.log(lineEndGroup);
                            console.log('-------------------');

                            var c;
                            var ctm;

                            if (thisGroupId == lineStartGroup) {
                                var t = d3.transform(d3.select('#' + thisGroupId).attr('transform')).translate;

                                if (lineStartSide == 'left') {
                                    c = d3.select('#' + lineStartGroup).select('.left');
                                    ctm = c.node().getCTM();
                                } else {
                                    c = d3.select('#' + lineStartGroup).select('.right');
                                    ctm = c.node().getCTM();
                                }

                                var x = d3.transform(c.select(function () {
                                    return this.parentNode;
                                }).attr('transform')).translate;


                                console.log('START e'+ctm['e']);
                                console.log('START ex'+x[0]);
                                console.log('START f'+ctm['f']);
                                console.log('START fx'+x[0]);
                                line.attr('x1', Number(c.attr('cx')) + Number(x[0]));
                                line.attr('y1', Number(c.attr('cy')) + Number(x[1]));
                            }

                            if (thisGroupId == lineEndGroup) {
//                                var t = d3.transform(d3.select('#' + thisGroupId).attr('transform')).translate;
//
//                                line.attr('x2', Number(line.attr('real-x2')) + Number(t[0]));
//                                line.attr('y2', Number(line.attr('real-y2')) + Number(t[1]));

                                if (lineEndSide == 'left') {
                                    c = d3.select('#' + lineEndGroup).select('.left');

                                } else {
                                    c = d3.select('#' + lineEndGroup).select('.right');

                                }

                                var x = d3.transform(c.select(function () {
                                    return this.parentNode;
                                }).attr('transform')).translate;

                                ctm = c.node().getCTM();
                                console.log('END circleId'+c.attr('id'));

                                line.attr('x2', Number(c.attr('cx')) + Number(x[0]));
                                line.attr('y2', Number(c.attr('cy')) + Number(x[1]));

                            }


                        });


                    })

            ;

    opBox.call(drag);
    qBox.call(drag);
    circles.call(cDrag);

    var circleID;

    var dummyLine;

    circles.on('mouseover', function () {
        circleID = d3.select(this).attr('id');
    }).on('mouseout', function () {
        circleID = null;
    })

function getXY(mouse, current, parent) {

    var obj = {
        x: 0,
        y: 0
    };

    var dx = mouse.dx;
    var dy = mouse.dy;

    obj.x = dx;
    obj.y = dy;

    var xGap = current.x - parent.x;
    var yGap = current.y - parent.y;

    if (dx < 0) {
        if ((dx + xGap) < 0) {
            obj.x = -1 * xGap + 10;
        }
    } else {
        if ((dx + xGap) > parent.width) {
            obj.x = parent.width - xGap - current.width - 10;
        }
    }

    if (dy < 0) {
        if ((dy + yGap) < 0) {
            obj.y = -1 * yGap + 10;
        }

    } else {
        if ((dy + yGap) > parent.height) {
            obj.y = parent.height - yGap - current.height - 10;
        }
    }


    return obj;
}

</script>