template/html/d3-graph.js
function renderGraph(options)
   {
      options = typeof options === 'object' ? options : {};
      options.redrawOnly = typeof options.redrawOnly === 'boolean' ? options.redrawOnly : false;

      // Recycle all SVG elements above the first SVGGElement.
      recycleGraph();

      // Lines
      // Note: on second render link.source / target will be an object instead of a number.
      links = graph.append(getSVG('g')).selectAll('line')
       .data(data.links)
       .enter().append(getSVG('path'))
       .attr('class', 'link')
       .attr('marker-end', function() { return 'url(#regular)'; })
       .attr('data-source', function(link)
       {
          return typeof link.source === 'number' ? link.source : link.source.index;
       })
       .attr('data-target', function(link)
       {
          return typeof link.target === 'number' ? link.target : link.target.index;
       });

      // Nodes
      nodes = graph.append(getSVG('g')).selectAll('node')
       .data(data.nodes)
       .enter().append(getSVG('g'))
       .call(layout.drag)
       .attr('class', 'node');

      // Circles
      nodes.attr('class', function(node) { return formatClassName('node', node); });
      nodes.append(getSVG('circle'))
       .attr('id', function(node) { return formatClassName('id', node); })
       .attr('class', function(node) { return formatClassName('circle', node) + ' ' + node.packageData.jspmType; })
       .attr('r', circleRadius)
       .on('contextmenu', onNodeContextClick)
       .on('mousedown', onNodeMouseDown.bind(this, nodes, links))
       .on('mouseover', onNodeMouseOverOut.bind(this, nodes, links, true))
       .on('mouseout', onNodeMouseOverOut.bind(this, nodes, links, false))
       .on('dblclick.zoom', function(node) // Centers view on node.
       {
          d3.event.stopPropagation();

          var dcx = (window.innerWidth / 2 - node.x * zoom.scale());
          var dcy = (window.innerHeight / 2 - node.y * zoom.scale());

          zoom.translate([dcx, dcy]);

          graph.transition()
           .duration(500)
           .attr('transform', 'translate('+ dcx + ',' + dcy  + ')scale(' + zoom.scale() + ')');
       });

      // A copy of the text with a thick white stroke for legibility.
      nodes.append(getSVG('text'))
       .attr('x', 15)
       .attr('y', '.31em')
       .attr('class', function(node) { return 'shadow ' + formatClassName('text', node); })
       .text(function(node)
       {
          return (appOptions.showFullNames ? node.packageData.actualPackageName : node.packageData.packageName) +
           ' (' + node.minLevel +')';
       });

      nodes.append(getSVG('text'))
       .attr('class', function(node)
       {
          return node.packageData.isAlias ? 'isAliased ' : '' + formatClassName('text', node);
       })
       .attr('x', 15)
       .attr('y', '.31em')
       .text(function(node)
       {
          return (appOptions.showFullNames ? node.packageData.actualPackageName : node.packageData.packageName) +
           ' (' + node.minLevel +')';
       });

      // Set the force layout nodes / links and start the bounce.
      layout.nodes(data.nodes);
      layout.links(data.links);
      layout.start();

      linkedByIndex = {};

      // Build linked index
      data.links.forEach(function(node) { linkedByIndex[node.source.index + ',' + node.target.index] = true; });

      // Set a low alpha to provide minimal bounce when just redrawing.
      if (options.redrawOnly) { layout.alpha(0.01); }
   }
function renderGraph(options)
   {
      options = typeof options === 'object' ? options : {};
      options.redrawOnly = typeof options.redrawOnly === 'boolean' ? options.redrawOnly : false;

      // Recycle all SVG elements above the first SVGGElement.
      recycleGraph();

      // Lines
      // Note: on second render link.source / target will be an object instead of a number.
      links = graph.append(getSVG('g')).selectAll('line')
       .data(data.links)
       .enter().append(getSVG('path'))
       .attr('class', 'link')
       .attr('marker-end', function() { return 'url(#regular)'; })
       .attr('data-source', function(link)
       {
          return typeof link.source === 'number' ? link.source : link.source.index;
       })
       .attr('data-target', function(link)
       {
          return typeof link.target === 'number' ? link.target : link.target.index;
       });

      // Nodes
      nodes = graph.append(getSVG('g')).selectAll('node')
       .data(data.nodes)
       .enter().append(getSVG('g'))
       .call(layout.drag)
       .attr('class', 'node');

      // Circles
      nodes.attr('class', function(node) { return formatClassName('node', node); });
      nodes.append(getSVG('circle'))
       .attr('id', function(node) { return formatClassName('id', node); })
       .attr('class', function(node) { return formatClassName('circle', node) + ' ' + node.packageData.jspmType; })
       .attr('r', circleRadius)
       .on('contextmenu', onNodeContextClick)
       .on('mousedown', onNodeMouseDown.bind(this, nodes, links))
       .on('mouseover', onNodeMouseOverOut.bind(this, nodes, links, true))
       .on('mouseout', onNodeMouseOverOut.bind(this, nodes, links, false))
       .on('dblclick.zoom', function(node) // Centers view on node.
       {
          d3.event.stopPropagation();

          var dcx = (window.innerWidth / 2 - node.x * zoom.scale());
          var dcy = (window.innerHeight / 2 - node.y * zoom.scale());

          zoom.translate([dcx, dcy]);

          graph.transition()
           .duration(500)
           .attr('transform', 'translate('+ dcx + ',' + dcy  + ')scale(' + zoom.scale() + ')');
       });

      // A copy of the text with a thick white stroke for legibility.
      nodes.append(getSVG('text'))
       .attr('x', 15)
       .attr('y', '.31em')
       .attr('class', function(node) { return 'shadow ' + formatClassName('text', node); })
       .text(function(node)
       {
          return (appOptions.showFullNames ? node.packageData.actualPackageName : node.packageData.packageName) +
           ' (' + node.minLevel +')';
       });

      nodes.append(getSVG('text'))
       .attr('class', function(node)
       {
          return node.packageData.isAlias ? 'isAliased ' : '' + formatClassName('text', node);
       })
       .attr('x', 15)
       .attr('y', '.31em')
       .text(function(node)
       {
          return (appOptions.showFullNames ? node.packageData.actualPackageName : node.packageData.packageName) +
           ' (' + node.minLevel +')';
       });

      // Set the force layout nodes / links and start the bounce.
      layout.nodes(data.nodes);
      layout.links(data.links);
      layout.start();

      linkedByIndex = {};

      // Build linked index
      data.links.forEach(function(node) { linkedByIndex[node.source.index + ',' + node.target.index] = true; });

      // Set a low alpha to provide minimal bounce when just redrawing.
      if (options.redrawOnly) { layout.alpha(0.01); }
   }
Renders the graph after recycling any nodes above the top level `g` SVGGElement.

@param {object}   options - Optional parameters:
```
(boolean)   redrawOnly - sets layout.alpha to a low value to reduce bounce.
```