Cannot get treemap to render with new JSON data - json

I am using the d3.js treemap in a an application with backbone.js. The treemap renders correctly with the first JSOn data, but subsequent calls with different JSON data do not cause the treemap to re-render.
My HTML looks like this:
<html>
<head>
<title>Jenkins analytics</title>
<!-- stylesheets -->
<link rel="stylesheet" href="css/spa.css" type="text/css"/>
<link rel="stylesheet" href="css/treemap.css" type="text/css"/>
</head>
<body>
<nav>
<form>
<fieldset>
<label for="chart">Chart:</label>
<select id="chart" name="chart">
<option value="treemap" selected>Treemap</option>
<option value="motion">MotionChart</option>
</select>
</fieldset>
</form>
<form>
<fieldset>
<label for="period">Period:</label>
<select id="period" name="period">
<option value="lastday" selected>Day</option>
<option value="lastweek">Week</option>
<option value="lastmonth">Month</option>
</select>
<label for="team">Team:</label>
<select id="team" name="team">
<option value="all" selected>all</option>
<option value="spg">spg</option>
<option value="beacon">beacon</option>
<option value="disco">disco</option>
</select>
<label for="status">Status:</label>
<select id="status" name="status">
<option value="" selected>all</option>
<option value="SUCCESS">success</option>
<option value="FAILURE">failure</option>
<option value="ABORTED">aborted</option>
</select>
</fieldset>
</form>
<form>
<fieldset>
<label for="duration">Duration</label>
<input id="duration" type="radio" name="mode" value="size" checked />
<label for="count">Count</label>
<input id="count" type="radio" name="mode" value="count" />
<label for="average">Average</label>
<input id="average" type="radio" name="mode" value="avg" />
</fieldset>
</form>
</nav>
<div id="container" />
<!-- Third party javascript -->
<script type="text/javascript" src="http://code.jquery.com/jquery-2.0.3.min.js" charset="utf-8"></script>
<script type="text/javascript" src="script/underscore/underscore.js" charset="utf-8"></script>
<script type="text/javascript" src="script/backbone/backbone.js" charset="utf-8"></script>
<script type="text/javascript" src="script/d3/d3.v3.js" charset="utf-8"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<!-- Application javascript -->
<script type="text/javascript" src="script/module.js"></script>
<!-- Startup -->
<script>
var navview = new NavViewer();
</script>
</body>
</html>
script/module.js looks like this:
var NavViewer = Backbone.View.extend({
el: 'nav',
events: {
"change #chart": "change_chart",
"change #period": "change_period",
"change #team": "change_team",
"change #status": "change_status"
},
initialize: function() {
console.log("NavViewer.initialize");
this.d3view = new D3Viewer();
this.active_view = this.d3view;
},
change_chart: function(e) {
console.log("NavViewer.change_chart");
},
change_period: function(e) {
var _period = $('#period').val();
console.log("NavViewer.change_period to " + _period);
this.active_view.load();
},
change_team: function(e) {
var _team = $('#team').val();
console.log("NavViewer.change_team to "+ _team);
this.active_view.load();
},
change_status: function(e) {
var _status = $('#status').val();
console.log("NavViewer.change_status to " + _status);
this.active_view.load();
}
});
var JenkinsViewer = Backbone.View.extend({
el: '#container',
server: "http://192.168.1.100:5000",
url_fragment: function() {
var _period = $('#period').val();
var _team = $('#team').val();
var _status = $('#status').val();
return "when=" + _period +
(_team == "all" ? "" : ("&" + "team=" + _team)) +
(_status == "" ? "" : ("&" + "status=" + _status));
}
});
var D3Viewer = JenkinsViewer.extend({
initialize: function() {
this.margin = {top: 8, right: 0, bottom: 0, left: 0};
this.width = 1200 - this.margin.left - this.margin.right;
this.height = 800 - this.margin.top - this.margin.bottom - 60;
this.container = d3.select(this.el);
this.color = d3.scale.category20c();
this.base_url = this.server + "/team_build";
this.treemap = d3.layout.treemap()
.size([this.width, this.height])
.sticky(true)
.value(function(d) { return d.size; });
this.position = function() {
this.style("left", function(d) { return d.x + "px"; })
.style("top", function(d) { return d.y + "px"; })
.style("width", function(d) { return Math.max(0, d.dx - 1) + "px"; })
.style("height", function(d) { return Math.max(0, d.dy - 1) + "px"; });
};
/* style the container */
this.container
.style("position", "relative")
.style("width", this.width + "px")
.style("height", this.height + "px")
.style("left", this.margin.left + "px")
.style("top", this.margin.top + "px")
.style("border", "1px solid black");
/* tootlip is appended to container */
this.tooltip = this.container.append("div")
.attr('class', 'tooltip')
.style("visibility", "hidden")
.style("background-color", "#ffffff");
this.load();
},
load: function() {
var $container = this.container;
var color = this.color;
var treemap = this.treemap;
var position = this.position;
var tooltip = this.tooltip;
var url = this.base_url + "?" + this.url_fragment();
console.log("D3View.load: " + url);
d3.json(url, function(error, root) {
/* 'root' actually means the data retrieved by the xhr call */
var node = $container.datum(root)
.selectAll(".node")
.data(treemap.nodes);
node.enter().append("div")
.attr("class", "node")
.call(position)
.style("background", function(d) { return d.children ? color(d.name) : null; })
.text(function(d) { return d.children ? null : d.name; })
.on("mouseover", function(d) {
tooltip.html(d.name + ": " + Math.floor(d.value))
.style("visibility", "visible");
this.style.cursor = "hand";
})
.on("mouseout", function(){
tooltip.style("visibility", "hidden");
});
d3.selectAll("input").on("change", function change() {
var functions = {
count: function(d) { return d.count; },
size: function(d) { return d.size; },
avg: function(d) { return d.size / d.count; }
};
var value = functions[this.value];
node
.data(treemap.value(value).nodes)
.transition()
.duration(1500)
.call(position);
});
});
return true;
}
});
Here are things I've done:
read the d3.js code for d3.json and d3.layout.treemap
googled the living daylights out of this
read Lars Kotthoff's d3.js answers on StackOverflow
read some articles: Enter and Exit, D3.js: How to handle dynamic JSON Data
tried treemap.sticky(false)
tried adding node.exit().remove()
I think the problem might relate to stickiness or the absence of a call node.exit().remove(). I've tried modifying both of those without success. However, to get the interactive clientside UI, I think I need to use treemap.sticky(true).
I've confirmed that I get different JSON each time I hit the REST API service. I've confirmed that container.datum().children changes in size between calls, confirming for me that it is a question of treemap not re-rendering.
D3.js: How to handle dynamic JSON Data looks highly relevant:
"It's worth mentioning that if you already have data with the same key, d3.js will store the data in the node but will still use the original data."
"When I started playing with d3.js, I thought that rendered data would be evented and react to my changing of scales, domains and whatnot. It's not. You have to tell d3.js to update the current stuff or it will stay there, unsynced."
"I assign the result of data() to a variable because enter() only affects new data that are not bound to nodes."

Here is what I ended up doing in my load method:
load: function() {
var $container = this.container;
var color = this.color;
var treemap = this.treemap;
var position = this.position;
var tooltip = this.tooltip;
var url = this.base_url + "?" + this.url_fragment();
console.log("D3View.load: " + url);
d3.json(url, function(error, root) {
var tooltip_mouseover = function(d) {
tooltip.html(d.name + ": " + Math.floor(d.value))
.style("visibility", "visible");
this.style.cursor = "hand";
};
var tooltip_mouseout = function(){
tooltip.style("visibility", "hidden");
};
var background_color = function(d) { return d.children ? color(d.name) : null; };
var text_format = function(d) { return d.children ? null : d.name; };
/*
* Refresh sticky bit
* https://github.com/mbostock/d3/wiki/Treemap-Layout
* "Implementation note: sticky treemaps cache the array of nodes internally; therefore, it
* is not possible to reuse the same layout instance on multiple datasets. To reset the
* cached state when switching datasets with a sticky layout, call sticky(true) again."
*/
treemap.sticky(true);
/* 'root' actually means the data retrieved by the xhr call */
var nodes = $container.datum(root)
.selectAll(".node")
.data(treemap.nodes);
var enter = nodes.enter().append("div")
.attr("class", "node")
.call(position)
.style("background", background_color)
.text(text_format)
.on("mouseover", tooltip_mouseover)
.on("mouseout", tooltip_mouseout);
var exit = nodes.exit().remove();
var update = nodes.style("background", background_color)
.call(position)
.text(text_format);
d3.selectAll("input").on("change", function change() {
var functions = {
count: function(d) { return d.count; },
size: function(d) { return d.size; },
avg: function(d) { return d.size / d.count; }
};
var value = functions[this.value];
$container.selectAll(".node")
.data(treemap.value(value).nodes)
.transition()
.duration(1500)
.call(position);
});
});
There are two important changes:
Reset treemap sticky bit
Use enter/exit/update selections for new/missing/changed nodes/data
Notice also that enter nodes have the side effect of adding a div of class node and the exit and update nodes do not reference that div-class -- except they do, in the creation of nodes. If add a further selection on node-class in those places, your selection will be empty and the exit and update code will be no-ops.
Thanks to AmeliaBR for posting a really helpful comment with a link to an SO answer she composed.
Other helpful reads:
https://github.com/mbostock/d3/wiki/Selections
http://bost.ocks.org/mike/join/

Related

D3 - Updating Choropleth csv to change mapping of colours

Hi I'm new to D3 and am having trouble updating my map through buttons that trigger a function to load a new csv. I looked at similar posts which mentioned that I would only need to update the section which fills the map with colours, and have tried to adapt this but clicking on my button doesn't affect the map.
Here is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3: Setting path fills dynamically to generate a choropleth</title>
<h1>Relation between Managers and Tertiary Education </h1>
<div id="option1">
<input type="button"value="Education"onclick="viewEducation()" />
</div>
<div id="option2">
<input type="button"value="Managers"onclick="viewManagers()" />
</div>
<script type="text/javascript" src="d3.js"></script>
<style type="text/css">
/* No style rules here yet */
</style>
</head>
<body>
<script type="text/javascript">
//Width and height
var w = 1000;
var h = 600;
var path = d3.geoPath()
.projection(d3.geoMercator()
.center([151,-33.5])
.scale(17000)
.translate([w/2,h/2]));
//Define quantize scale to sort data values into buckets of color
var color = d3.scaleQuantize()
.range(["rgb(237,248,233)","rgb(200,235,198)", "rgb(186,228,179)","rgb(146,208,140)", "rgb(116,196,118)", "rgb(88,178,100)", "rgb(49,163,84)", "rgb(20,130,60)", "rgb(0,109,44)"])
.domain([6274, 39796]);
//Colors derived from ColorBrewer, by Cynthia Brewer, and included in
//https://github.com/d3/d3-scale-chromatic
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Load in agriculture data
d3.csv("ManagerPercentage.csv", function(d) { d.value = parseFloat(d.value); parseFloat(d.value1); return d; }, function(data) {
//Set input domain for color scale
color.domain([
d3.min(data, function(d) { return d.value; }),
d3.max(data, function(d) { return d.value; })
]);
//Load in GeoJSON data
d3.json("australia_adm2.json", function(json) {
//Merge the ag. data and GeoJSON
//Loop through once for each ag. data value
for (var i = 0; i < data.length; i++) {
//Grab state name
var dataState = data[i].state;
//Grab data value, and convert from string to float
var dataValue = parseFloat(data[i].value);
//Find the corresponding state inside the GeoJSON
for (var j = 0; j < json.features.length; j++) {
var jsonState = json.features[j].properties.sa4_name11;
if (dataState == jsonState) {
//Copy the data value into the JSON
json.features[j].properties.value = dataValue;
//Stop looking through the JSON
break;
}
}
}
//Bind data and create one path per GeoJSON feature
svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.style("fill", function(d) {
//Get data value
var value = d.properties.value;
if (value) {
//If value exists…
return color(value);
} else {
//If value is undefined…
return "#ccc";
}
});
});
});
function viewEducation () {
d3.select("option1")
d3.csv("EducationPercentage.csv", function(data) {
svg.selectAll("path")
.style("fill", function(d) {
var value = d.properties.value;
if (value) {
return color(value);
} else {
return "#ccc";
}
});
});
}
function viewManagers () {
d3.select("option2")
d3.csv("ManagerPercentage.csv", function(data) {
svg.selectAll("path")
.style("fill", function(d) {
var value = d.properties.value;
if (value) {
return color(value);
} else {
return "#ccc";
}
});
});
});
}
</script>
</body>
</html>

Using dynamic input(CSV) in d3js

I am trying to use dynamic input to a draw function in d3js. So when the user changes the csv it would remove the current selection and draw the visualization for the new input. So my question is would I be using a onChange function with the select and then within this function parse the csv and call for the draw function.The current working code is here in plunker:
https://plnkr.co/edit/AjVBK3rTOF5aI4eDDbV5?p=preview
<svg width="1250" height="1080"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width");
var format = d3.format(",d");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var pack = d3.pack()
.size([width, width])
.padding(1.5);
var inputs = {};
function selectCity(){
//storing the drop-dsown selection in the ddSelection var
var ddSelection = document.getElementById("city").value;
//feeding that to create the csv filename you want
var str1 = ddSelection;
var str2 = ".csv";
var csvFile = str1.concat(str2);
str1.concat(str2);
console.log(csvFile);
d3.csv(csvFile, function(d) {
d.sno = +d.sno;
return d;
}, function(error, data) {
if (error) throw error;
d3.selectAll("input").on("change", function(){
inputs[this.id] = +this.value;
console.log(inputs.myValue + "-" + inputs.myRating)
if(inputs.myValue && inputs.myRating){
var classes = data.filter(d => d.value < inputs.myValue && d.rating >= inputs.myRating);
draw(classes);
}
})
function draw(classes) {
console.log(classes.length);
var root = d3.hierarchy({
children: classes
})
.sum(function(d) {
return d.value;
})
.each(function(d) {
if (id = d.data.id) {
var id, i = id.lastIndexOf(".");
d.id = id;
d.package = id.slice(0, i);
d.class = id.slice(i + 1);
}
});
var node = svg.selectAll(".node")
.data(pack(root).leaves())
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("circle")
.attr("id", function(d) {
return d.id;
})
.attr("r", function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.package);
});
node.append("clipPath")
.attr("id", function(d) {
return "clip-" + d.id;
})
.append("use")
.attr("xlink:href", function(d) {
return "#" + d.id;
});
node.append("text")
.attr("clip-path", function(d) {
return "url(#clip-" + d.id + ")";
})
.selectAll("tspan")
.data(function(d) {
return d.class.split(/(?=[A-Z][^A-Z])/g);
})
.enter().append("tspan")
.attr("x", 0)
.attr("y", function(d, i, nodes) {
return 13 + (i - nodes.length / 2 - 0.5) * 10;
})
.text(function(d) {
return d;
});
node.append("title")
.text(function(d) {
return d.data.id + "\n" + format(d.value);
});
}
});
}
</script>
</div>
Here is one example how to do it: http://www.d3noob.org/2014/04/using-html-inputs-with-d3js.html
You don't have to redraw everything but update certain elements.
I don't understand your part about changing the CSV. The user does not change the CSV but your visual output is depending on some user data. So yes, within the callback function of d3.csv(), you write your code that calls some kind of a draw function. But the draw function does not have to be defined there. You can write the function outside and just call it there. This increased the readable of your code dramatically. ;)

how to replace comma with dot

I am having comma values inside my json and i want when i get those values i get them in dot so basically I want to convert my comma values into dot values..my json looks like and its always fixed that i will get comma values at val003.
I know something to do like var new = new.replace(/,/g, '.') . but how can i specify my val003 here for the conversion. Thank you in advance
My html
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta content="utf-8" http-equiv="encoding">
<div id="below">
<div id="chart"></div>
</div>
<script>
var jsonURL = 'avb.json';
var myData = [];
var fliterdata = [];
var tempdata = [];
var selectop = "";
var selectDate = false;
var chartType = chartType || 'bar';
function filterJSON(json, key, value) {
var result = [];
for (var foo in json) {
var extractstr = json[foo][key] ;
extractstr=String(extractstr);
if (extractstr.slice(3)== value) {
result.push(json[foo]);
}
}
return result;
}
function selectValue(d) {
switch (selectop) { //d object select particular value for Y axis
case "01":
return d.val001;
break;
case "02":
return d.val002;
break;
case "03":
return d.val003;
break;
case "04":
return d.val004;
break;
default:
//console.log("default");
return d.val001;
}
}
var line = d3.svg.line()
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(selectValue(d));
})
.interpolate("monotone")
.tension(0.9);
yScale.domain([0, d3.max(tempData, function(d) {
return +selectValue(d);
})]);
var svg = d3.select('#chart').append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
if (chartType == 'bar') {
svg
.selectAll(".bar") //makes bar
.data(tempData)
.enter().append("rect")
.attr("class", "bar")
.style("fill", "teal")
.attr("x", function(d) {
return xScale(d.date);
}).attr("width", xScale.rangeBand())
.attr("y", function(d) {
return yScale(selectValue(d));
}).attr("height", function(d) {
console.log("as", d.value);
return height - yScale(selectValue(d));
})
}
if (chartType == 'line') {
svg.append("path") // Add the line path.
.data(tempData)
.attr("class", "line")
.attr("d", line(tempData));
}
}
d3.json(jsonURL, function(data) {
myData = data; //data from json in mydata
d.val003.replace(",",".")
myData.forEach(function(d) {
d.date = new Date(d.date);
d.date = new Date(d.date + " UTC");
});
$("#listbox").on("click", function() {
var key = $(this).val();
console.log("key:", key);
var value = $('#listbox option:selected').text();
console.log("vaue:", value);
selectop = String(key);
selectop = selectop.slice(-2);
console.log("mydata: ", myData);
console.log("selectops:", selectop);
fliterdata = filterJSON(myData, key, value); //selected value from user and picks the whole element that contains that attribute
console.log("fliterdata: ", fliterdata);
tempData = fliterdata; //graph made by temp data
if (selectDate)
render(true);
});
});
function selectChartType(type) {
chartType = type;
render(true);
}
</script>
</body>
</div>
</body>
</html>
Try this,
return d.val003.toString().replace(",",".");
Yo can simply request a value in a JSon object -- it pretty much serves as an object in JavaScript.
So if you have your JSon object, lets call it json you can simply do:
var url = *your url*, json;
// retrieve the json object from a URL
$.getJSON(url, function (response) {$
json = response;
});
// reassing val003 with the corrected string
json.val003 = json.val003.replace(",", ".")
That should work, I believe.
If it is always the comma in a decimal number you want to replace, than you can do a search replace in the whole json string for the sequence "number" "comma" "number" like:
([0-9]),([0-9])
and replace it with:
$1.$2
$1 and $2 are the placeholders for the found numbers before and after the comma.
You can use this site for online testing:
http://www.regexe.com/

HTML Drag and Drop and Struts

I've been trying to implement the new "Drag and Drop" file and upload feature, using the code from SitePoint.com. I am using Struts Framework as well. FormFile made my uploads easier, I could just hit on "choose" and click away. But, I just can't seem to get it to work the DnD with Struts. The ActionForm validates and reports that the file size is 0! Here's the code from SitePoint.com (js):
(function() {
// getElementById
function $id(id) {
return document.getElementById(id);
}
// output information
function Output(msg) {
var m = $id("messages");
m.innerHTML = msg + m.innerHTML;
}
// file drag hover
function FileDragHover(e) {
e.stopPropagation();
e.preventDefault();
e.target.className = (e.type == "dragover" ? "hover" : "");
}
// file selection
function FileSelectHandler(e) {
// cancel event and hover styling
FileDragHover(e);
// fetch FileList object
var files = e.target.files || e.dataTransfer.files;
// process all File objects
for (var i = 0, f; f = files[i]; i++) {
ParseFile(f);
UploadFile(f);
}
}
// output file information
function ParseFile(file) {
Output(
"<p>File information: <strong>" + file.name +
"</strong> type: <strong>" + file.type +
"</strong> size: <strong>" + file.size +
"</strong> bytes</p>"
);
// display an image
if (file.type.indexOf("image") == 0) {
var reader = new FileReader();
reader.onload = function(e) {
Output(
"<p><strong>" + file.name + ":</strong><br />" +
'<img src="' + e.target.result + '" /></p>'
);
}
reader.readAsDataURL(file);
}
// display text
if (file.type.indexOf("text") == 0) {
var reader = new FileReader();
reader.onload = function(e) {
Output(
"<p><strong>" + file.name + ":</strong></p><pre>" +
e.target.result.replace(/</g, "<").replace(/>/g, ">") +
"</pre>"
);
}
reader.readAsText(file);
}
else{
var reader=new FileReader();
reader.readAsDataURL(file);
}
}
// upload JPEG files
function UploadFile(file) {
var xhr = new XMLHttpRequest();
if (xhr.upload) {
// create progress bar
var o = $id("progress");
var progress = o.appendChild(document.createElement("p"));
progress.appendChild(document.createTextNode("upload " + file.name));
// progress bar
xhr.upload.addEventListener("progress", function(e) {
var pc = parseInt(100 - (e.loaded / e.total * 100));
progress.style.backgroundPosition = pc + "% 0";
}, false);
// file received/failed
xhr.onreadystatechange = function(e) {
if (xhr.readyState == 4) {
progress.className = (xhr.status == 200 ? "success" : "failure");
}
};
// start upload
xhr.open("POST", $id("upload").action, true);
xhr.setRequestHeader("X_FILENAME", file.name);
xhr.send(file);
}
}
// initialize
function Init() {
var fileselect = $id("fileselect"),
filedrag = $id("filedrag"),
submitbutton = $id("submitbutton");
// file select
fileselect.addEventListener("change", FileSelectHandler, false);
// is XHR2 available?
var xhr = new XMLHttpRequest();
if (xhr.upload) {
// file drop
filedrag.addEventListener("dragover", FileDragHover, false);
filedrag.addEventListener("dragleave", FileDragHover, false);
filedrag.addEventListener("drop", FileSelectHandler, false);
filedrag.style.display = "block";
}
}
// call initialization file
if (window.File && window.FileList && window.FileReader) {
Init();
}
})();
The jsp (just messing around):
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%#page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Upload File</title>
<link href="css/styles.css" rel="stylesheet" type="text/css" media="all"/>
</head>
<body>
<form id="upload" action="../Repositore/upload_file.do" method="POST" enctype="multipart/form-data">
<fieldset>
<legend>File Upload Form</legend>
<input type="hidden" id="MAX_FILE_SIZE" name="MAX_FILE_SIZE" value="20000000" />
<div>
<label for="fileselect">File to upload:</label>
<input type="file" id="fileselect" name="file" multiple="multiple" />
<div id="filedrag">or drop files here</div>
</div>
<div>
<table border='0' cellpadding='2'>
<tr><td><label for="ftags">Tags:</label></td><td> <input type='text' id="ftags" name='tags'/></td><td>(Separate by commas)</td></tr>
<tr><td><label for="desc">Description:</label></td><td> <textarea name="description" id="desc" rows="5" cols="30"></textarea></td></tr>
<tr><td><input type="checkbox" name="comment">Yes</input></td></tr>
</div>
<div id="submitbutton">
<button type="submit">Upload file</button>
</div>
</fieldset>
</form>
<div id="progress"></div>
<div id="messages">
<p>Status:</p>
</div>
<script src="js/filedrag.js" type="text/javascript"></script>
What could be wrong? Help?

Django d3.js in HTML file not loading unless I manually open HTML file

I'm trying to implement a bubble chart on a webpage using the Django framework and the D3.js framework. I have an HTML file, and my data is stored in a JSON file.
Accessing the JSON file seems fine, because when I manually open the HTML file in my browser it works/loads fine, however, when I hit the webpage URL endpoint via Django, the D3 part of the page just shows blank (console says "node is undefined"). I am using the example code given here.
Any help would be very appreciated. Thanks.
D3 js code:
<script src="http://d3js.org/d3.v3.min.js"></script>
<div class="cloud">
<script>
var diameter = 960,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("body").append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
d3.json("cloud.json", function(error, root) {
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root))
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
});
// Returns a flattened hierarchy containing all leaf nodes under the root.
function classes(root) {
var classes = [];
function recurse(name, node) {
if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
else classes.push({packageName: name, className: node.name, value: node.size});
}
recurse(null, root);
return {children: classes};
}
d3.select(self.frameElement).style("height", diameter + "px");
</script>