Mario Lepore Mario Lepore - 1 month ago 23
Javascript Question

how to update data form file json using d3.js (zoomable circle pack)

I'm working on a real-time visualization of incoming data stored in a json file. I use D3 for the visualization. This is the chart I use: http://mbostock.github.io/d3/talk/20111116/pack-hierarchy.html

enter image description here

This is all the code page:

<body onload="visualize()">

<h2>
<input type="button" value="Get new data"
onclick='ajaxSyncRequest("get-current-time")' /> <br /> <br />
Message from server :: <span id="message"></span>
</h2>

<script type="text/javascript">

function ajaxSyncRequest(reqURL) {
//Creating a new XMLHttpRequest object
var xmlhttp;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest(); //for IE7+, Firefox, Chrome, Opera, Safari
} else {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); //for IE6, IE5
}
//Create a asynchronous GET request
xmlhttp.open("GET", reqURL, false);
xmlhttp.send(null);

//Execution blocked till server send the response
if (xmlhttp.readyState == 4) {
if (xmlhttp.status == 200) {
document.getElementById("message").innerHTML = xmlhttp.responseText;
//alert(xmlhttp.responseText);
update();

} else {
alert('Something is wrong !!');
}
}
}
</script>



<script type="text/javascript" src="d3/d3.js"></script>
<script type="text/javascript" src="d3/d3.layout.js"></script>
<script type="text/javascript">
function visualize() {

var w = 1280, h = 800, r = 720, x = d3.scale.linear().range(
[ 0, r ]), y = d3.scale.linear().range([ 0, r ]), node, root;

var pack = d3.layout.pack().size([ r, r ]).value(function(d) {
return d.size;
})

var vis = d3.select("body").insert("svg:svg", "h2")
.attr("width", w).attr("height", h).append("svg:g").attr(
"transform",
"translate(" + (w - r) / 2 + "," + (h - r) / 2
+ ")");

d3.json("flare.json", function(data) {
node = root = data;

var nodes = pack.nodes(root);

vis.selectAll("circle").data(nodes).enter()
.append("svg:circle").attr("class", function(d) {
return d.children ? "parent" : "child";
}).attr("cx", function(d) {
return d.x;
}).attr("cy", function(d) {
return d.y;
}).attr("r", function(d) {
return d.r;
}).on("click", function(d) {
return zoom(node == d ? root : d);
});

vis.selectAll("text").data(nodes).enter().append("svg:text")
.attr("class", function(d) {
return d.children ? "parent" : "child";
}).attr("x", function(d) {
return d.x;
}).attr("y", function(d) {
return d.y;
}).attr("dy", ".35em").attr("text-anchor", "middle")
.style("opacity", function(d) {
return d.r > 20 ? 1 : 0;
}).text(function(d) {
return d.name;
});

d3.select(window).on("click", function() {
zoom(root);
});
});

function zoom(d, i) {
var k = r / d.r / 2;
x.domain([ d.x - d.r, d.x + d.r ]);
y.domain([ d.y - d.r, d.y + d.r ]);

var t = vis.transition().duration(d3.event.altKey ? 7500 : 750);

t.selectAll("circle").attr("cx", function(d) {
return x(d.x);
}).attr("cy", function(d) {
return y(d.y);
}).attr("r", function(d) {
return k * d.r;
});

t.selectAll("text").attr("x", function(d) {
return x(d.x);
}).attr("y", function(d) {
return y(d.y);
}).style("opacity", function(d) {
return k * d.r > 20 ? 1 : 0;
});

node = d;
d3.event.stopPropagation();
}
}
</script>

<script type="text/javascript">
function update() {

var w = 1280, h = 800, r = 720, x = d3.scale.linear().range(
[ 0, r ]), y = d3.scale.linear().range([ 0, r ]), node, root;

var pack = d3.layout.pack().size([ r, r ]).value(function(d) {
return d.size;
})

var vis = d3.select("svg").attr("width", w).attr("height", h)
.append("svg:g").attr(
"transform",
"translate(" + (w - r) / 2 + "," + (h - r) / 2
+ ")");

d3.json("flare.json", function(data) {

node = root = data;

var nodes = pack.nodes(root);
// DATA JOIN
// Join new data with old elements, if any.
var newG = vis.selectAll("circle").data(nodes);

// UPDATE
// Update old elements as needed.
newG.attr("class", "update").transition();

// ENTER
// Create new elements as needed.
newG.enter().append("svg:circle").attr("class", function(d) {
return d.children ? "parent" : "child";
}).attr("cx", function(d) {
return d.x;
}).attr("cy", function(d) {
return d.y;
}).attr("r", function(d) {
return d.r;
}).on("click", function(d) {
return zoom(node == d ? root : d);
});

newG.enter().append("svg:text")
.attr("class", function(d) {
return d.children ? "parent" : "child";
}).attr("x", function(d) {
return d.x;
}).attr("y", function(d) {
return d.y;
}).attr("dy", ".35em").attr("text-anchor", "middle")
.style("opacity", function(d) {
return d.r > 20 ? 1 : 0;
}).text(function(d) {
return d.name;
});

// EXIT
// Remove old elements as needed.
newG.exit().attr("class", "exit").transition().remove();

d3.select(window).on("click", function() {
zoom(root);
});
});

function zoom(d, i) {
var k = r / d.r / 2;
x.domain([ d.x - d.r, d.x + d.r ]);
y.domain([ d.y - d.r, d.y + d.r ]);

var t = vis.transition().duration(d3.event.altKey ? 7500 : 750);

t.selectAll("circle").attr("cx", function(d) {
return x(d.x);
}).attr("cy", function(d) {
return y(d.y);
}).attr("r", function(d) {
return k * d.r;
});

t.selectAll("text").attr("x", function(d) {
return x(d.x);
}).attr("y", function(d) {
return y(d.y);
}).style("opacity", function(d) {
return k * d.r > 20 ? 1 : 0;
});

node = d;
d3.event.stopPropagation();
}
}
</script>




I wanted to dynamically update the chart from the new file that I get from the server. But with this function it draw a new chart on the old. I have tried different solution to update the chart but none of them worked.
How I have to modify the code to get the dynamically update?

Answer

The following changes are necessary to make this work. First, you need to select the existing SVG element (and its descendant g) as vis in the update function:

var vis = d3.select("svg > g");

Then, you need to compute and handle enter, update and exit selections for circles and text separately:

var newG = vis.selectAll("circle").data(nodes);

newG.enter().append("svg:circle");
newG.exit().remove();
newG.attr("class", function(d) {
    return d.children ? "parent" : "child";
  }).attr("cx", function(d) {
    return d.x;
  }).attr("cy", function(d) {
    return d.y;
  }).attr("r", function(d) {
    return d.r;
  }).on("click", function(d) {
    return zoom(node == d ? root : d);
  });

var texts = vis.selectAll("text").data(nodes);

texts.enter().append("svg:text");
texts.exit().remove();
texts.append("svg:text")
  .attr("class", function(d) {
    return d.children ? "parent" : "child";
  }).attr("x", function(d) {
    return d.x;
  }).attr("y", function(d) {
    return d.y;
  }).attr("dy", ".35em")
  .attr("text-anchor", "middle")
  .style("opacity", function(d) {
    return d.r > 20 ? 1 : 0;
  }).text(function(d) {
    return d.name;
  }); 

You could also merge your functions as they both do almost the same thing and there's a lot of redundant code. But the above should be sufficient to make it work.

Comments