title: D3 force directed graph drag and zoom functions not working
body: <p>I've been trying to implement a D3 force directed graph function to give a visual representation to my data, So far I've been successful in getting the nodes to display on the screen and have the individual nodes draggable, however I have been unsuccessful in getting the whole network to pan and zoom.</p>
  
  <p>I've looked at countless examples online, but haven't really been able to figure out what I'm doing wrong.</p>
  
  <p>Could someone point me in the right direction please, using d3 version 4</p>
  
  <pre><code>function selectableForceDirectedGraph(){
  
  var width = d3.select('svg').attr('width');
  var height = d3.select('svg').attr('height');
  
  var color = d3.scaleOrdinal(d3.schemeCategory20);
  
  var svg = d3.select("svg")
  .attr('width',width)
  .attr('height',height);
  
  var container = svg.append("g")
  .on("zoom",zoomed)
  .on("start",dragstarted)
  .on("drag",dragged)
  .on("end",dragended);
  
  var json_nodes = _dict['one']['nodes'];
  var json_links = _dict['one']['links'];
  
  var simulation = d3.forceSimulation()
      .force("link", d3.forceLink().id(function(d){return d.id}))
      .force("charge", d3.forceManyBody())
      .force("center", d3.forceCenter(width / 2, height / 2));
  
    var link = svg.append("g")
      .attr("class", "links")
      .selectAll("line")
      .data(json_links)
      .enter().append("line")
      .attr("stroke-width", function(d) { return Math.sqrt(d.value); })
      .style("marker-end","url(#suit)");
  
    var node = svg.append("g")
      .attr("class", "nodes")
      .selectAll("circle")
      .data(json_nodes)
      .enter().append("circle")
      .attr("r", 5)
      .attr("fill", function(d) { return color(d.group); })
      .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended));
  
    node.append("title")
        .text(function(d) { return d.id; });
  
    simulation
        .nodes(json_nodes)
        .on("tick", ticked);
  
    simulation.force("link")
        .links(json_links);
  
    function ticked() {
      link
          .attr("x1", function(d) { return d.source.x; })
          .attr("y1", function(d) { return d.source.y; })
          .attr("x2", function(d) { return d.target.x; })
          .attr("y2", function(d) { return d.target.y; });
  
      node
          .attr("cx", function(d) { return d.x; })
          .attr("cy", function(d) { return d.y; });
    }
  
  function dragstarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
  }
  
  function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }
  
  function dragended(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }
  
  //Zoom functions 
  function zoomed(){
      container.attr("transform","translate(" + d3.event.translate + ")scale(" +d3.event.scale + ")");
  }
  }
  </code></pre>
  
  <p>The HTML doc that this function is called from has an SVG instantiated (hence why the SVG is selected, not appended like in most other examples)</p>
  
  <p>The two variables "json_nodes" and "json_links" take formatted nodes and links (like what you'd see in a JSON file) from a text file and passes them as the data (wanted to do this offline so when I go overseas). The format for the data is like so:</p>
  
  <p>nodes:[{"id": "Name", "group": integer},...],</p>
  
  <p>links:[{"source": "Name", "target": "Name", "value": integer},...]</p>
  
  <p>I apologise if this is a repeated question, I haven't been able to find any really intuitive help with this.</p>