如何将标记头放在链接的中间

时间:2016-01-29 11:22:06

标签: javascript html5 css3 d3.js svg

我想在链接的中间放置一个标记,而不是像我的代码那样将它放在最后。

虽然我用Google搜索,但我找不到代码的解决方案。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
      <script src="../D3/d3.min.js"></script>
  </head>

  <body>
      <style>
          body {
              background-color: #3a5795;
          }



svg:not(.active):not(.ctrl) {
  cursor: crosshair;
}

path.link {
  fill: none;
  stroke:floralwhite;
  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;
}

rect.node {
  stroke-width: 1.5px;
  cursor: pointer;
}

rect.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;
}

 </style>
      <script type="text/javascript">
          // set up SVG for D3
          var width = 1400,
              height = 800,
              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 rect).
          //  - links are always source < target; edge directions are set by 'left' and 'right'.
          var nodes = [
     {
         "id": "Component",
         "description": "Component are the Containers",
          "type":"wiring"
     },
     {
         "id": "Form Design And Data Design",
         "description": "In the Form Design and Data Design we can create form and data",
         "type": "wiring"
     },
     {
         "id": "Data and Property ",
         "description": "All the Data has the Property and value Associated with It",
          "type":"wiring"
     },
     {
         "id": "Entity Query",
         "description": "Entity Queries can be used to create Entity Relationship ",
         "type": "wiring"
     },
     {
         "id": "Entity Query and Entity Data",
         "description": "Entity Data Can be used to create ",
         "type": "wiring"
     }
          ],
            lastNodeId = 2,
            links = [

            ];

          // init D3 force layout
          var force = d3.layout.force()
              .nodes(nodes)
              .links(links)
              .size([width, height])
              .linkDistance(250)
              .charge(-1000)
              .gravity(0.05)
              .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', 6)
              .attr('markerHeight', 5)
              .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'),


              rect = 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 wrapText(text, width) {
              text.each(function () {
                  var textEl = d3.select(this),
                      words = textEl.text().split(/\s+/).reverse(),
                      word,
                      line = [],
                      linenumber = 0,
                      lineHeight = 1.1, // ems
                      y = textEl.attr('y'),
                      dx = parseFloat(textEl.attr('dx') || 0),
                      dy = parseFloat(textEl.attr('dy') || 0),
                      tspan = textEl.text(null).append('tspan').attr('x', 0).attr('y', y).attr('dy', dy + 'em');

                  while (word = words.pop()) {
                      line.push(word);
                      tspan.text(line.join(' '));
                      if (tspan.node().getComputedTextLength() > width) {
                          line.pop();
                          tspan.text(line.join(' '));
                          line = [word];
                          tspan = textEl.append('tspan').attr('x', 0).attr('y', y).attr('dx', dx).attr('dy', ++linenumber * lineHeight + dy + 'em').text(word);
                      }
                  }
              });
          }



         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 ? 17 : 12,
                      targetPadding = d.right ? 17 : 12,
                      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;
              });

              rect.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();


              // rect (node) group
              // NB: the function arg is crucial here! nodes are known by id, not by index!
              rect = rect.data(nodes, function (d) { return d.id; });

              // update existing nodes (reflexive & selected visual states)
              rect.selectAll('rect')
                .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; });

              // add new nodes
              var g = rect.enter().append('svg:g');

              //g.append('svg:rect')
              //  .attr('class', 'node')
              //  .attr('r', 30)
              g.append('svg:rect')
            .attr('class', 'node')
            .attr('width', 150)
            .attr("height", 60)
            .attr("rx", 30)
            .attr("ry", 30)
            .attr("x", -75)
            .attr("y", -16.5)
            .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 < target; arrows separately specified by booleans
                    var source, target, direction;
                    if (mousedown_node.id < 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 && 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; })
                  .call(wrapText, 100);

              // remove old nodes
              rect.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) {
                  rect.call(force.drag);
                  svg.classed('ctrl', true);
              }

              if (!selected_node && !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) {
                 rect
                    .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();



</script>
  </body>

</html>

1 个答案:

答案 0 :(得分:2)

要在链接的中点绘制标记,您可以使用marker-mid,其工作方式与marker-startmarker-end非常相似,只是它在中间插入了一个标记元素。

path.enter().append('svg:path')
            .style('marker-mid', function (d) { return 'url(#start-arrow)'; })

出于演示目的,我刚刚使用了start-arrow,这当然可以根据您的喜好进行调整。

但是,只有在中点有一个顶点时才会绘制标记。对于您的代码而言,情况并非如此,因为您从源到目标绘制一条直线,仅定义起点和终点。另一方面,有一条直线就派上用场了,因为计算中点并将直线分成两个部分相当容易,从而在中间插入一个新的顶点。您的tick()处理程序中已经进行了计算,给出了中间结果以帮助找到中间点:

// Coordinates of mid point on line to add new vertex.
midX = (targetX - sourceX) / 2 + sourceX;   
midY = (targetY - sourceY) / 2 + sourceY;

//                                    | v --- new vertex --- v |               
return 'M' + sourceX + ',' + sourceY + 'L' + midX + ',' + midY + 'L' + targetX + ',' + targetY;

请查看以下代码片段以了解正常工作。

&#13;
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
      <script src="../D3/d3.min.js"></script>
  </head>

  <body>
      <style>
          body {
              background-color: #3a5795;
          }



svg:not(.active):not(.ctrl) {
  cursor: crosshair;
}

path.link {
  fill: none;
  stroke:floralwhite;
  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;
}

rect.node {
  stroke-width: 1.5px;
  cursor: pointer;
}

rect.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;
}

 </style>
      <script type="text/javascript">
          // set up SVG for D3
          var width = 1400,
              height = 800,
              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 rect).
          //  - links are always source < target; edge directions are set by 'left' and 'right'.
          var nodes = [
     {
         "id": "Component",
         "description": "Component are the Containers",
          "type":"wiring"
     },
     {
         "id": "Form Design And Data Design",
         "description": "In the Form Design and Data Design we can create form and data",
         "type": "wiring"
     },
     {
         "id": "Data and Property ",
         "description": "All the Data has the Property and value Associated with It",
          "type":"wiring"
     },
     {
         "id": "Entity Query",
         "description": "Entity Queries can be used to create Entity Relationship ",
         "type": "wiring"
     },
     {
         "id": "Entity Query and Entity Data",
         "description": "Entity Data Can be used to create ",
         "type": "wiring"
     }
          ],
            lastNodeId = 2,
            links = [

            ];

          // init D3 force layout
          var force = d3.layout.force()
              .nodes(nodes)
              .links(links)
              .size([width, height])
              .linkDistance(250)
              .charge(-1000)
              .gravity(0.05)
              .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', 6)
              .attr('markerHeight', 5)
              .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'),


              rect = 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 wrapText(text, width) {
              text.each(function () {
                  var textEl = d3.select(this),
                      words = textEl.text().split(/\s+/).reverse(),
                      word,
                      line = [],
                      linenumber = 0,
                      lineHeight = 1.1, // ems
                      y = textEl.attr('y'),
                      dx = parseFloat(textEl.attr('dx') || 0),
                      dy = parseFloat(textEl.attr('dy') || 0),
                      tspan = textEl.text(null).append('tspan').attr('x', 0).attr('y', y).attr('dy', dy + 'em');

                  while (word = words.pop()) {
                      line.push(word);
                      tspan.text(line.join(' '));
                      if (tspan.node().getComputedTextLength() > width) {
                          line.pop();
                          tspan.text(line.join(' '));
                          line = [word];
                          tspan = textEl.append('tspan').attr('x', 0).attr('y', y).attr('dx', dx).attr('dy', ++linenumber * lineHeight + dy + 'em').text(word);
                      }
                  }
              });
          }



         function resetMouseVars() {
              mousedown_node = null;
              mouseup_node = null;
              mousedown_link = null;
          }

          // update force layout (called automatically each iteration)
          function tick() {
                console.log(path);
              // 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 ? 17 : 12,
                      targetPadding = d.right ? 17 : 12,
                      sourceX = d.source.x + (sourcePadding * normX),
                      sourceY = d.source.y + (sourcePadding * normY),
                      targetX = d.target.x - (targetPadding * normX),
                      targetY = d.target.y - (targetPadding * normY),
                      midX = (targetX - sourceX) / 2 + sourceX,
                      midY = (targetY - sourceY) / 2 + sourceY;
                
                  return 'M' + sourceX + ',' + sourceY + 'L' + midX + ',' + midY + 'L' + targetX + ',' + targetY;
              });

              rect.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-mid', function (d) { return '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-mid', function (d) { return '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();


              // rect (node) group
              // NB: the function arg is crucial here! nodes are known by id, not by index!
              rect = rect.data(nodes, function (d) { return d.id; });

              // update existing nodes (reflexive & selected visual states)
              rect.selectAll('rect')
                .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; });

              // add new nodes
              var g = rect.enter().append('svg:g');

              //g.append('svg:rect')
              //  .attr('class', 'node')
              //  .attr('r', 30)
              g.append('svg:rect')
            .attr('class', 'node')
            .attr('width', 150)
            .attr("height", 60)
            .attr("rx", 30)
            .attr("ry", 30)
            .attr("x", -75)
            .attr("y", -16.5)
            .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 < target; arrows separately specified by booleans
                    var source, target, direction;
                    if (mousedown_node.id < 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 && 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; })
                  .call(wrapText, 100);

              // remove old nodes
              rect.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) {
                  rect.call(force.drag);
                  svg.classed('ctrl', true);
              }

              if (!selected_node && !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) {
                 rect
                    .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();



</script>
  </body>

</html>
&#13;
&#13;
&#13;