1

I'm playing around with D3 and making force directed graphs. I want to clear and reload the graph in response to clicks on buttons. However, when I reload a graph that has already been loaded, the graph doesn't format properly. This is the code I'm using:

<!DOCTYPE html>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v2.js?2.9.1"></script>
<style>

.link {
  fill: none;
  stroke: #666;
  stroke-width: 1.5px;
}

.node circle {
  fill: #ccc;
  stroke: #fff;
  stroke-width: 1.5px;
}

text {
  font: 10px sans-serif;
  pointer-events: none;
}

</style>
<body>
<script>

var all = [
{source: "Tockwotton", target: "List", type: "woodworking"},
{source: "Tockwotton", target: "Prince Lab", type: "woodworking"},
{source: "Prince Lab", target: "Prince Lab", type: "metalworking"},
{source: "Tockwotton", target: "List", type: "cnc"},
{source: "Granoff", target: "Prince Lab", type: "3d-printing"},
{source: "Tockwotton", target: "List", type: "drafting"},
{source: "List", target: "Prince Lab", type: "welding"},
{source: "Prince Lab", target: "Prince Lab", type: "sand-blaster"},
{source: "Tockwotton", target: "List", type: "finishing"},
{source: "Tockwotton", target: "Prince Lab", type: "finishing"},
{source: "Tockwotton", target: "Tockwotton", type: "laser-cutter"}
];

var wood = [
{source: "Tockwotton", target: "List", type: "woodworking"},
{source: "Tockwotton", target: "Prince Lab", type: "woodworking"}
];

var metal = [
{source: "Prince Lab", target: "Prince Lab", type: "metalworking"}
];

var cnc = [
{source: "Tockwotton", target: "List", type: "cnc"}
];

var print = [
{source: "Granoff", target: "Prince Lab", type: "3d-printing"}
];

var draft = [
{source: "Tockwotton", target: "List", type: "drafting"}
];

var weld = [
{source: "List", target: "Prince Lab", type: "welding"}
];

var sand = [
{source: "Prince Lab", target: "Prince Lab", type: "sand-blaster"}
];

var finishing = [
{source: "Tockwotton", target: "List", type: "finishing"},
{source: "Tockwotton", target: "Prince Lab", type: "finishing"}
];

var laser = [
{source: "Tockwotton", target: "Tockwotton", type: "laser-cutter"}
];

function render(linkdata){  

d3.selectAll("svg").remove();

links = linkdata;

nodes = {};

// Compute the distinct nodes from the links.
links.forEach(function(link) {
  link.source = nodes[link.source] || (nodes[link.source] = {name: link.source, type: link.type});
  link.target = nodes[link.target] || (nodes[link.target] = {name: link.target, type: link.type});
});

width = 960,
    height = 500;

force = d3.layout.force()
    .nodes(d3.values(nodes))
    .links(links)
    .size([width, height])
    .linkDistance(60)
    .charge(-300)
    .on("tick", tick)
    .start();

svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

link = svg.selectAll(".link")

    .data(force.links())
    .enter().append("line")
    .attr("class", "link");

node = svg.selectAll(".node")
    .data(force.nodes())
  .enter().append("g")
    .attr("class", "node")
    .on("mouseover", mouseover)
    .on("mouseout", mouseout)
    .call(force.drag);

node.append("circle")
    .attr("r", 8);

node.append("text")
    .attr("x", 12)
    .attr("dy", ".35em")
    .text(function(d) { return d.name; });

function tick() {
  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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}

function mouseover() {
  d3.select(this).select("circle").transition()
      .duration(750)
      .attr("r", 16);
}

function mouseout() {
  d3.select(this).select("circle").transition()
      .duration(750)
      .attr("r", 8);
}
}
</script>

<div id="nav">
    <div id="wood">
        <a onclick="render(wood)">Woodworking</a>
    </div>
    <div id="metal">
        <a onclick="render(metal)">Metalworking</a>
    </div>
    <div id="cnc">
        <a onclick="render(cnc)">CNC</a>
    </div>
    <div id="print">
        <a onclick="render(print)">3D Printing</a>
    </div>
    <div id="draft">
        <a onclick="render(draft)">Drafting</a>
    </div>
    <div id="weld">
        <a onclick="render(weld)">Welding</a>
    </div>
    <div id="sand">
        <a onclick="render(sand)">Sand Blast</a>
    </div>
    <div id="finishing">
        <a onclick="render(finishing)">Finishing</a>
    </div>
    <div id="laser">
        <a onclick="render(laser)">Laser Cutting</a>
    </div>
    <div id="all">
        <a onclick="render(all)">All</a>
    </div>
</div>

</body>

Try it and note that the first time you click on an item, it works fine, but if you click on it again, you just get one node called [object Object]. Why is this and how can I fix this?

VividD
  • 10,456
  • 6
  • 64
  • 111
Alex H Hadik
  • 774
  • 2
  • 7
  • 16

1 Answers1

1

When you use links = linkdata, it's probably not doing what you expect. Like some other languages, this doesn't copy the data; instead, it just makes links point to the same place that linkdata points to. When you loop through links, modifying them to point at each other, you're modifying the original objects in the arrays. When you loop through them again, they've already been modified, leading to the undesirable behavior you're experiencing.

To avoid this fate, you'll need to deep-copy the array before you modify it. There's quite a few ways to do that, but you'll want to make sure that whatever solution you're using will clone the array as an array without turning it into a plain Object.

Community
  • 1
  • 1
icktoofay
  • 126,289
  • 21
  • 250
  • 231
  • I deep copied the JSON array with links = JSON.parse(JSON.stringify(linkdata)); and it works. Not the best solution but at least it gets me to the next step. Thanks for the help! – Alex H Hadik Jul 17 '13 at 05:29