1

I'm trying to create a link between two desired nodes manually (with the help of mouse pointer). For that i'm selecting both nodes from mouse one by one. A link (path) is creating from the first selected Node. This Link will be following the mouse pointer. And if i click on any other node, A link will be created between that and first node.

But there is an issue with the link(path). It is not actually following the mouse pointer if the SVG is transformed (translated and scaled).

Path is correctly following mouse pointer if the SVG is not Transformed

Pic 1: Path is started from the clicked node position and following the mouse pointer. Here SVG is not Transformed (zoom not applied).

Path is not properly following mouse pointer if the SVG is Zoomed in (Transformed) Pic 2: Path is started from the clicked node position but not properly following the mouse pointer. Here SVG is Transformed (zoom applied here).

Below is the sample code in which one SVG is created inside another SVG. This is the requirement of my project for later purpose.

I have tried alot but not understanding where is the issue. Here is my code : JSFiddle

var dnodes = [];
var dlinks = [];
var default_link_color = "#bbb";
var link_default_width = 2;
var min_zoom = 0.1;
var max_zoom = 7;


function initdiagram(el) {
var im = this;
var w = $(el).innerWidth();
var h = $(el).innerHeight();

var connector = d3.svg.line().interpolate("linear");

var zoom = d3.behavior.zoom().scaleExtent([min_zoom, max_zoom]);

var selectedNode1 = null;
var selectedNode2 = null;

var mainsvg = d3.select(el).append("svg")
    .attr("class","mainsvg")
    .attr("width", w)
    .attr("height", h)
    .on("mousemove", function(){
      if(selectedNode1 !== null)
      {
        var cm = d3.mouse(this);
        templink.attr("d",function(){
          return connector([[selectedNode1.x  , selectedNode1.y ], [ cm[0]  , cm[1]]]);
        });
      }
      if((selectedNode1 !== null) && (selectedNode2 !== null))
      {
        templink.attr("d",null);
      }

    });

var svg = mainsvg.append("svg")
      .attr("class","svg")
      .attr("viewBox", "0 0 " + w + " " + h)
      .attr("preserveAspectRatio", "xMidYMid");
var g = svg.append("g")
      .attr('width', w)
              .attr('height', h);


zoom.on("zoom", function() {
    g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
      });

mainsvg.call(zoom).on("dblclick.zoom", null); //Disabling zoom on double click

function findNode(id) {
    for (var i in dnodes) {
        if (dnodes[i]["id"] === id) return dnodes[i];
    }
}

this.addNode = function(id) {
  var newNode = findNode(id);
  if (newNode == undefined) {
    dnodes.push({
      "id": id,
      fixed:false
    });
  }
}

this.addLink = function(sourceId, targetId) {
  var sourceNode = findNode(sourceId);
  var targetNode = findNode(targetId);
  if ((sourceNode !== undefined) && (targetNode !== undefined)) {
      dlinks.push({
        "source": sourceNode,
        "target": targetNode
      });
  }
}

var force = self.force = d3.layout.force()
      .linkDistance(160)
      .charge(-2000)
  .on("tick",tick)
  .size([w, h]);

  var nodes = force.nodes(dnodes);
  var links = force.links(dlinks);


this.forcestart = function(){
  force.start();
} 

function tick() {
  node.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")";});
  link.attr('d', function(d) { var path='M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y; return path});  
}


this.forceupdate = function(){
  update(dnodes, dlinks);
}


var node = null;
var link = null;
var templink = null;

function update(dnodes, dlinks) {
  g.selectAll(".link").remove(); 
      g.selectAll(".node").remove();
  g.selectAll(".templink").remove();

  link = g.selectAll(".link")
    .attr("class", "link")
          .data(dlinks)
          .enter()
    .append('path')
    .attr('d', function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y;})
    .attr("class","edgepath")
    .attr('id', function(d,i) {return 'edgepath'+i;})
    .style("stroke",default_link_color)
    .style("stroke-width",link_default_width)
    .style("pointer-events", "none");


templink = g.append("path")
    .attr("class","templink")
    .attr("d",null)
    .style("stroke","green")
    .style("stroke-width",link_default_width+1)
    .style("stroke-dasharray","5, 5")
    .style("fill","none"); 

node = g.selectAll(".node")
    .data(dnodes)
    .enter().append("g")
    .attr("class", "node");

node.append("rect")
    .attr("width", 20)
    .attr("height", 20)
    .attr("x", -10)
    .attr("y", -10)
    .style("fill", "blue");

node.on("mouseup", function(d) { 
    if(selectedNode1 == null)
    {
      selectedNode1 = d;
    }

  });

  }
return this;
}

var di = new initdiagram("#graph");

di.addNode("i0");
di.addNode("i1");
di.addNode("i2");

di.addLink("i0", "i1");
di.addLink("i1", "i2");

di.forceupdate();
di.forcestart();
text {
  font-family: sans-serif;
  pointer-events: none;
}
html,body { width:100%; height:100%; margin:none; padding:none; }
#graph { width:100%;height:100%; margin:auto; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="graph"></div>
Raghu
  • 85
  • 10

2 Answers2

1

Got idea from this post to resolve the issue.

Here is the Updated JSFiddle.

zoom.on("zoom", function() {
    translateVar[0] = d3.event.translate[0];
    translateVar[1] = d3.event.translate[1];
    scaleVar = d3.event.scale;
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
  });

And

var mainsvg = d3.select(el).append("svg")
.attr("class","mainsvg")
.attr("width", w)
.attr("height", h)
.on("mousemove", function(){
  if(selectedNode1 !== null)
  {
    var cm = d3.mouse(this);
    var newX = cm[0] - translateVar[0] ;
    var newY = cm[1] - translateVar[1] ;
    if(scaleVar > 0)
    {
        newX = newX / scaleVar;
        newY = newY / scaleVar;
    }
    templink.attr("d",function(){
      return connector([[selectedNode1.x  , selectedNode1.y ], [ newX  , newY]]);
    });
  }
  if((selectedNode1 !== null) && (selectedNode2 !== null))
  {
    templink.attr("d",null);
  }

});
Raghu
  • 85
  • 10
0

Try calculating the coordinates as shown below.

SVGPoint matrixTransform Applies a 2x3 matrix transformation on this SVGPoint object and returns a new, transformed SVGPoint object.

SVGGraphicsElement.getCTM() Returns a DOMMatrix representing the matrix that transforms the current element's coordinate system to its SVG viewport's coordinate system.

var pt = this.createSVGPoint();
pt.x = d3.event.x;
pt.y = d3.event.y;
pt = pt.matrixTransform(g.node().getCTM().inverse());
templink.attr("d", function() {
    return connector([
       [selectedNode1.x, selectedNode1.y],
       [pt.x, pt.y]
    ]);
});

var dnodes = [];
var dlinks = [];
var default_link_color = "#bbb";
var link_default_width = 2;
var min_zoom = 0.1;
var max_zoom = 7;


function initdiagram(el) {
  var im = this;
  var w = $(el).innerWidth();
  var h = $(el).innerHeight();

  var connector = d3.svg.line().interpolate("linear");

  var zoom = d3.behavior.zoom().scaleExtent([min_zoom, max_zoom]);

  var selectedNode1 = null;
  var selectedNode2 = null;

  var mainsvg = d3.select(el).append("svg")
    .attr("class", "mainsvg")
    .attr("width", w)
    .attr("height", h)
    .on("mousemove", function() {
      if (selectedNode1 !== null) {       
        var pt = this.createSVGPoint();
        pt.x = d3.mouse(this)[0];
        pt.y = d3.mouse(this)[1];
        pt = pt.matrixTransform(g.node().getCTM().inverse());
        templink.attr("d", function() {
          return connector([
            [selectedNode1.x, selectedNode1.y],
            [pt.x, pt.y]
          ]);
        });
      }
      if ((selectedNode1 !== null) && (selectedNode2 !== null)) {
        templink.attr("d", null);
      }

    });

  var svg = mainsvg.append("svg")
    .attr("class", "svg")
    .attr("viewBox", "0 0 " + w + " " + h)
    .attr("preserveAspectRatio", "xMidYMid");
  var g = svg.append("g")
    .attr('width', w)
    .attr('height', h);


  zoom.on("zoom", function() {
    g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
  });

  mainsvg.call(zoom).on("dblclick.zoom", null); //Disabling zoom on double click

  function findNode(id) {
    for (var i in dnodes) {
      if (dnodes[i]["id"] === id) return dnodes[i];
    }
  }

  this.addNode = function(id) {
    var newNode = findNode(id);
    if (newNode == undefined) {
      dnodes.push({
        "id": id,
        fixed: false
      });
    }
  }

  this.addLink = function(sourceId, targetId) {
    var sourceNode = findNode(sourceId);
    var targetNode = findNode(targetId);
    if ((sourceNode !== undefined) && (targetNode !== undefined)) {
      dlinks.push({
        "source": sourceNode,
        "target": targetNode
      });
    }
  }

  var force = self.force = d3.layout.force()
    .linkDistance(160)
    .charge(-2000)
    .on("tick", tick)
    .size([w, h]);

  var nodes = force.nodes(dnodes);
  var links = force.links(dlinks);


  this.forcestart = function() {
    force.start();
  }

  function tick() {
    node.attr("transform", function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    });
    link.attr('d', function(d) {
      var path = 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
      return path
    });
  }


  this.forceupdate = function() {
    update(dnodes, dlinks);
  }


  var node = null;
  var link = null;
  var templink = null;

  function update(dnodes, dlinks) {
    g.selectAll(".link").remove();
    g.selectAll(".node").remove();
    g.selectAll(".templink").remove();

    link = g.selectAll(".link")
      .attr("class", "link")
      .data(dlinks)
      .enter()
      .append('path')
      .attr('d', function(d) {
        return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
      })
      .attr("class", "edgepath")
      .attr('id', function(d, i) {
        return 'edgepath' + i;
      })
      .style("stroke", default_link_color)
      .style("stroke-width", link_default_width)
      .style("pointer-events", "none");


    templink = g.append("path")
      .attr("class", "templink")
      .attr("d", null)
      .style("stroke", "green")
      .style("stroke-width", link_default_width + 1)
      .style("stroke-dasharray", "5, 5")
      .style("fill", "none");

    node = g.selectAll(".node")
      .data(dnodes)
      .enter().append("g")
      .attr("class", "node");

    node.append("rect")
      .attr("width", 20)
      .attr("height", 20)
      .attr("x", -10)
      .attr("y", -10)
      .style("fill", "blue");

    node.on("mouseup", function(d) {
      if (selectedNode1 == null) {
        selectedNode1 = d;
      }

    });

  }
  return this;
}

var di = new initdiagram("#graph");

di.addNode("i0");
di.addNode("i1");
di.addNode("i2");

di.addLink("i0", "i1");
di.addLink("i1", "i2");

di.forceupdate();
di.forcestart();
text {
  font-family: sans-serif;
  pointer-events: none;
}

html,
body {
  width: 100%;
  height: 100%;
  margin: none;
  padding: none;
}

#graph {
  width: 100%;
  height: 100%;
  margin: auto;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="graph"></div>
Gilsha
  • 14,431
  • 3
  • 32
  • 47