title: Adding label to dynamically added edge d3js force layout
body: <p>I have the following code to draw edges dynamically between circles by simply dragging from one node to another node. </p>
  
  <p>On clicking the SVG a new node will be added , a new connection can be drawn by dragging from one node to another node. While adding this edge is there a way to add label to this edge with svg <code>text</code> element ?</p>
  
  <p>I tried adding the following code:</p>
  
  <pre><code>drag_line.append('svg:text')
                          .attr('x', 50)
                          .attr('y', 20)
                          .attr('class', 'id')
                          .text(function(d) {
                              return "Label";
                          });
  </code></pre>
  
  <p>in the <code>mousedown</code> function which adds the line. But does not seem to work ? What am I doing wrong?</p>
  
  <p>Following actions can be perfromed:</p>
  
  <p>Drag from one node to another to create an edge</p>
  
  <p><code>cntrl</code> + drag to move the node alone. </p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>// Code goes here
  console.log("init");
  console.log(d3);
  // set up SVG for D3
  var width  = 960,
      height = 500,
      colors = d3.scale.category10();
  
  var svg = d3.select('body')
    .append('svg')
    .attr('oncontextmenu', 'return false;')
    .attr('width', width)
    .attr('height', height);
  
  // set up initial nodes and links
  //  - nodes are known by 'id', not by index in array.
  //  - reflexive edges are indicated on the node (as a bold black circle).
  //  - links are always source &lt; target; edge directions are set by 'left' and 'right'.
  var nodes = [
      {id: 0, reflexive: false},
      {id: 1, reflexive: true },
      {id: 2, reflexive: false}
    ],
    lastNodeId = 2,
    links = [
      {source: nodes[0], target: nodes[1], left: false, right: true },
      {source: nodes[1], target: nodes[2], left: false, right: true }
    ];
  
  // init D3 force layout
  var force = d3.layout.force()
      .nodes(nodes)
      .links(links)
      .size([width, height])
      .linkDistance(150)
      .charge(-500)
      .on('tick', tick)
  
  // define arrow markers for graph links
  svg.append('svg:defs').append('svg:marker')
      .attr('id', 'end-arrow')
      .attr('viewBox', '0 -5 10 10')
      .attr('refX', 6)
      .attr('markerWidth', 3)
      .attr('markerHeight', 3)
      .attr('orient', 'auto')
    .append('svg:path')
      .attr('d', 'M0,-5L10,0L0,5')
      .attr('fill', '#000');
  
  svg.append('svg:defs').append('svg:marker')
      .attr('id', 'start-arrow')
      .attr('viewBox', '0 -5 10 10')
      .attr('refX', 4)
      .attr('markerWidth', 3)
      .attr('markerHeight', 3)
      .attr('orient', 'auto')
    .append('svg:path')
      .attr('d', 'M10,-5L0,0L10,5')
      .attr('fill', '#000');
  
  // line displayed when dragging new nodes
  var drag_line = svg.append('svg:path')
    .attr('class', 'link dragline hidden')
    .attr('d', 'M0,0L0,0');
  
  // handles to link and node element groups
  var path = svg.append('svg:g').selectAll('path'),
      circle = svg.append('svg:g').selectAll('g');
  
  // mouse event vars
  var selected_node = null,
      selected_link = null,
      mousedown_link = null,
      mousedown_node = null,
      mouseup_node = null;
  
  function resetMouseVars() {
    mousedown_node = null;
    mouseup_node = null;
    mousedown_link = null;
  }
  
  // update force layout (called automatically each iteration)
  function tick() {
    // draw directed edges with proper padding from node centers
    path.attr('d', function(d) {
      var deltaX = d.target.x - d.source.x,
          deltaY = d.target.y - d.source.y,
          dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY),
          normX = deltaX / dist,
          normY = deltaY / dist,
          sourcePadding = d.left ? 42 : 37,
          targetPadding = d.right ? 42 : 37,
          sourceX = d.source.x + (sourcePadding * normX),
          sourceY = d.source.y + (sourcePadding * normY),
          targetX = d.target.x - (targetPadding * normX),
          targetY = d.target.y - (targetPadding * normY);
      return 'M' + sourceX + ',' + sourceY + 'L' + targetX + ',' + targetY;
    });
  
    circle.attr('transform', function(d) {
      return 'translate(' + d.x + ',' + d.y + ')';
    });
  }
  
  // update graph (called when needed)
  function restart() {
    // path (link) group
    path = path.data(links);
  
    // update existing links
    path.classed('selected', function(d) { return d === selected_link; })
      .style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
      .style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; });
  
  
    // add new links
    path.enter().append('svg:path')
      .attr('class', 'link')
      .classed('selected', function(d) { return d === selected_link; })
      .style('marker-start', function(d) { return d.left ? 'url(#start-arrow)' : ''; })
      .style('marker-end', function(d) { return d.right ? 'url(#end-arrow)' : ''; })
      .on('mousedown', function(d) {
        if(d3.event.ctrlKey) return;
  
        // select link
        mousedown_link = d;
        if(mousedown_link === selected_link) selected_link = null;
        else selected_link = mousedown_link;
        selected_node = null;
        restart();
      });
  
    // remove old links
    path.exit().remove();
  
  
    // circle (node) group
    // NB: the function arg is crucial here! nodes are known by id, not by index!
    circle = circle.data(nodes, function(d) { return d.id; });
  
    // update existing nodes (reflexive &amp; selected visual states)
    circle.selectAll('circle')
      .style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
      .classed('reflexive', function(d) { return d.reflexive; })
      .classed("fixed", function(d){ return d.fixed = true});
  
    // add new nodes
    var g = circle.enter().append('svg:g');
  
    g.append('svg:circle')
      .attr('class', 'node')
      .attr('r', 40)
      .style('fill', function(d) { return (d === selected_node) ? d3.rgb(colors(d.id)).brighter().toString() : colors(d.id); })
      .style('stroke', function(d) { return d3.rgb(colors(d.id)).darker().toString(); })
      .classed('reflexive', function(d) { return d.reflexive; })
      .on('mouseover', function(d) {
        if(!mousedown_node || d === mousedown_node) return;
        // enlarge target node
        d3.select(this).attr('transform', 'scale(1.1)');
      })
      .on('mouseout', function(d) {
        if(!mousedown_node || d === mousedown_node) return;
        // unenlarge target node
        d3.select(this).attr('transform', '');
      })
      .on('mousedown', function(d) {
        if(d3.event.ctrlKey) return;
  
        // select node
        mousedown_node = d;
        if(mousedown_node === selected_node) selected_node = null;
        else selected_node = mousedown_node;
        selected_link = null;
  
        // reposition drag line
        drag_line
          .style('marker-end', 'url(#end-arrow)')
          .classed('hidden', false)
          .attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + mousedown_node.x + ',' + mousedown_node.y);
  
        restart();
      })
      .on('mouseup', function(d) {
        if(!mousedown_node) return;
  
        // needed by FF
        drag_line
          .classed('hidden', true)
          .style('marker-end', '');
  
        // check for drag-to-self
        mouseup_node = d;
        if(mouseup_node === mousedown_node) { resetMouseVars(); return; }
  
        // unenlarge target node
        d3.select(this).attr('transform', '');
  
        // add link to graph (update if exists)
        // NB: links are strictly source &lt; target; arrows separately specified by booleans
        var source, target, direction;
        if(mousedown_node.id &lt; mouseup_node.id) {
          source = mousedown_node;
          target = mouseup_node;
          direction = 'right';
        } else {
          source = mouseup_node;
          target = mousedown_node;
          direction = 'left';
        }
  
        var link;
        link = links.filter(function(l) {
          return (l.source === source &amp;&amp; l.target === target);
        })[0];
  
        if(link) {
          link[direction] = true;
        } else {
          link = {source: source, target: target, left: false, right: false};
          link[direction] = true;
          links.push(link);
        }
  
        // select new link
        selected_link = link;
        selected_node = null;
        restart();
      });
  
    // show node IDs
    g.append('svg:text')
        .attr('x', 0)
        .attr('y', 4)
        .attr('class', 'id')
        .text(function(d) { return d.id; });
  
    // remove old nodes
    circle.exit().remove();
  
    // set the graph in motion
    force.start();
  }
  
  function mousedown() {
    // prevent I-bar on drag
    //d3.event.preventDefault();
  
    // because :active only works in WebKit?
    svg.classed('active', true);
  
    if(d3.event.ctrlKey || mousedown_node || mousedown_link) return;
  
    // insert new node at point
    var point = d3.mouse(this),
        node = {id: ++lastNodeId, reflexive: false};
    node.x = point[0];
    node.y = point[1];
    nodes.push(node);
  
    restart();
  }
  
  function mousemove() {
    if(!mousedown_node) return;
  
    // update drag line
    drag_line.attr('d', 'M' + mousedown_node.x + ',' + mousedown_node.y + 'L' + d3.mouse(this)[0] + ',' + d3.mouse(this)[1]);
  
    restart();
  }
  
  function mouseup() {
    if(mousedown_node) {
      // hide drag line
      drag_line
        .classed('hidden', true)
        .style('marker-end', '');
    }
  
    // because :active only works in WebKit?
    svg.classed('active', false);
  
    // clear mouse event vars
    resetMouseVars();
  }
  
  function spliceLinksForNode(node) {
    var toSplice = links.filter(function(l) {
      return (l.source === node || l.target === node);
    });
    toSplice.map(function(l) {
      links.splice(links.indexOf(l), 1);
    });
  }
  
  // only respond once per keydown
  var lastKeyDown = -1;
  
  function keydown() {
    d3.event.preventDefault();
  
    if(lastKeyDown !== -1) return;
    lastKeyDown = d3.event.keyCode;
  
    // ctrl
    if(d3.event.keyCode === 17) {
      circle.call(force.drag);
      svg.classed('ctrl', true);
    }
  
    if(!selected_node &amp;&amp; !selected_link) return;
    switch(d3.event.keyCode) {
      case 8: // backspace
      case 46: // delete
        if(selected_node) {
          nodes.splice(nodes.indexOf(selected_node), 1);
          spliceLinksForNode(selected_node);
        } else if(selected_link) {
          links.splice(links.indexOf(selected_link), 1);
        }
        selected_link = null;
        selected_node = null;
        restart();
        break;
      case 66: // B
        if(selected_link) {
          // set link direction to both left and right
          selected_link.left = true;
          selected_link.right = true;
        }
        restart();
        break;
      case 76: // L
        if(selected_link) {
          // set link direction to left only
          selected_link.left = true;
          selected_link.right = false;
        }
        restart();
        break;
      case 82: // R
        if(selected_node) {
          // toggle node reflexivity
          selected_node.reflexive = !selected_node.reflexive;
        } else if(selected_link) {
          // set link direction to right only
          selected_link.left = false;
          selected_link.right = true;
        }
        restart();
        break;
    }
  }
  
  function keyup() {
    lastKeyDown = -1;
  
    // ctrl
    if(d3.event.keyCode === 17) {
      circle
        .on('mousedown.drag', null)
        .on('touchstart.drag', null);
      svg.classed('ctrl', false);
    }
  }
  
  // app starts here
  svg.on('mousedown', mousedown)
    .on('mousemove', mousemove)
    .on('mouseup', mouseup);
  d3.select(window)
    .on('keydown', keydown)
    .on('keyup', keyup);
  restart();</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>/* Styles go here */
  
  svg {
    background-color: #FFF;
    cursor: default;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    user-select: none;
  }
  
  svg:not(.active):not(.ctrl) {
    cursor: crosshair;
  }
  
  path.link {
    fill: none;
    stroke: #000;
    stroke-width: 4px;
    cursor: default;
  }
  
  svg:not(.active):not(.ctrl) path.link {
    cursor: pointer;
  }
  
  path.link.selected {
    stroke-dasharray: 10,2;
  }
  
  path.link.dragline {
    pointer-events: none;
  }
  
  path.link.hidden {
    stroke-width: 0;
  }
  
  circle.node {
    stroke-width: 1.5px;
    cursor: pointer;
  }
  
  circle.node.reflexive {
    stroke: #000 !important;
    stroke-width: 2.5px;
  }
  
  text {
    font: 12px sans-serif;
    pointer-events: none;
  }
  
  text.id {
    text-anchor: middle;
    font-weight: bold;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"&gt;&lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>