Save selected D3.js Treemap nodes to local file - html

I am trying to finalize a D3.js treemp using this example code: https://bl.ocks.org/mbostock/4339083
I am running a local "python -m SimpleHTTPServer" web instance to serve up my pages.
I am not proficient in HTML or JavaScript.
I have a very large local JSON file that I created in python that is referenced in the "d3.json("d3_flare.json", function(error, flare)" in the index.html code snippet.
I need assistance with two items please:
I have very long text on the 4th node down from the JSON root node that D3.js & HTML will process. The text looks like:
"name": "Some Title: Some very very long text...Some very very long text...Some very very long text...Some very very long text...Some very very long text."
but can be longer or shorter.
How can I wrap this text to be more legible and neat while viewing in the browser at this position of JSON being read by D3.js & HTML given my attached index.html code snippet?
I need to save every selected node path(s) to a local file (e.g. CSV) that the user has clicked on, remember only running a local "python -m SimpleHTTPServer" web instance to serve up my pages.
How can I write the selected node paths to a local file given my attached index.html code snippet?
Thank you
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
/*var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
*/
var margin = {top: 40, right: 120, bottom: 40, left: 500},
width = 10000 - margin.right - margin.left,
height = 775 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("d3_flare.json", function(error, flare) {
if (error) throw error;
root = flare;
root.x0 = height / 2;
root.y0 = 0;
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
root.children.forEach(collapse);
update(root);
});
d3.select(self.frameElement).style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth original was 180
nodes.forEach(function(d) { d.y = d.depth * 900; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
.attr("y", function(d) { return d.children || d._children ? -13 : 13; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", 4.5)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
</script>

To solve my second question I ended up adding a contextmenu to the var nodeEnter section of my previously posted code where users are directed only to right-click a certain node in the path that gets recorded. The contextmenu:
.on("contextmenu", function (d) {
d3.event.preventDefault()
sessionStorage.setItem("selected_node", JSON.stringify(d.name));
var val = sessionStorage.getItem("selected_node");
post_session(val);
});
I then passed the sessionStorage objects to this function where I have cgi/bin python writing the requests to a file:
function post_session(session_store_info)
{
var http = new XMLHttpRequest();
http.open('POST', 'cgi-bin/post_session', true);
http.setRequestHeader('Content-Type', 'text/plain; charset=UTF-8');
http.send(session_store_info);
return http.status!=404;
}

Related

Create a Vertical Column Chart with D3 using CSV with Similar String Occurrences

I am seeking assistance with my script in creating a column chart with similar string occurrences listed in a CSV file. I am creating this chart using D3.js V3. The script and technique I’m using to capture the length of the items return results, but the number count of the length is not close to the actual count, very short from the expect item count. Can someone please review my script and see what is causing my length count to be way off from the actual number count. Any help, suggestion, or techniques will be greatly appreciated.
Below is a sample of the CSV file concept and D3 script. Thank you in advance.
d3.csv("../../data/teamData.csv", function(error, csv_data) {
if (error){
alert("Data didn't load, Refresh your browser");
}else{
for (var i = 0; i < csv_data.length; i++) {
if(csv_data[i].Team == "Team 1"){
team1 = csv_data[i].Team;
}
if(csv_data[i].Team == "Team 2"){
team2 = csv_data[i].Team;
}
}
}
var teamCount1 = team1.length;
var teamCount2 = team2.length;
console.log(teamCount1);//Not showing correct number count
console.log(teamCount2);//Not showing correct number count
var margin = {top: 2, right: 2, bottom: 60, left: 2},
w = 960 - margin.left - margin.right,
h = 500 - margin.top - margin.bottom;
var barPadding = 1;
var chartdata = [teamCount1, teamCount2];
var textData = ['Team 1', 'Team 2'];
var xScale = d3.scale.ordinal()
.domain(d3.range(chartdata2.length))
.rangeRoundBands([0, w], 0.05);
var yScale = d3.scale.linear()
.domain([0, d3.max(chartdata)])
.range([0, h]);
var svg = d3.select(".chartData")
.append("svg")
.attr("preserveAspectRatio", "xMidYMin")
.attr("viewBox", "0 0 1000 650")
.attr("width", "100%")
.attr("height", "100%")
.attr("display",'block')
.append("g").attr("class","stackChart2")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Create bars
svg.selectAll("barRect")
.data(chartdata)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
})
.on("click", function() {
sortBars();
});
//Create labels
svg.selectAll("text")
.data(chartdata)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d) - 10;
})
.attr("font-family", "sans-serif")
.attr("font-size", "14px")
.attr("fill", "black");
//Text Label
var yTextPadding = 20;
svg.selectAll(".bartext2")
.data(textData)
.enter()
.append("text")
.attr("class", "bartext2")
.attr("text-anchor", "middle")
.attr("fill", "black")
.attr("font-family", "sans-serif")
.style("font-size", "16px")
.attr("x", function(d,i) {return xScale(i)+xScale.rangeBand()/2;})
.attr("y", 460)
.text(function(d){return d;});
//Define sort order flag
var sortOrder = false;
//Define sort function
var sortBars = function() {
//Flip value of sortOrder
sortOrder = !sortOrder;
svg.selectAll("rect")
.sort(function(a, b) {
if (sortOrder) {
return d3.ascending(a, b);
} else {
return d3.descending(a, b);
}
})
.transition()
.delay(function(d, i) {
return i * 50;
})
.duration(1000)
.attr("x", function(d, i) {
return xScale(i);
});
}
});//End of D3 function
I was able to figure it out referencing this stackoverflow post: D3 Loading in CSV file then using only specific columns. I was able to filter out the needed column from my CSV file which was "Team", using d3 map as follow; var col2data = csv_data.map(function(d) { return d.Team });
Then I followed this stackoverflow post to filter my specific reoccurring item from the "Team" column: d3.js filter from csv file using multiple columns. This post made it possible to get the length of the items to build my column chart. All is working as expected. Below is the edited script. I hope this will help someone in the future encountering this issue.
d3.csv("../../data/teamData.csv", function(csv_data) {
//Filter CSV File to Specific Column
var col2data = csv_data.map(function(d) { return d.Team });
console.log(col2data);
//Filter CSV Column for Specific Column Item
var team1 = csv_data.filter(function(d){
if( d["Team"] == "Team 1"){
return d;
}
})
var team2 = csv_data.filter(function(d){
if( d["Team"] == "Team 2"){
return d;
}
})
console.log(team1);
var teamCount1 = team1.length;
var teamCount2 = team2.length;
//console.log(teamCount1);//Not showing correct number count
//console.log(teamCount2);//Not showing correct number count
var margin = {top: 2, right: 2, bottom: 60, left: 2},
w = 960 - margin.left - margin.right,
h = 500 - margin.top - margin.bottom;
var barPadding = 1;
var chartdata = [teamCount1, teamCount2];
var textData = ['Team 1', 'Team 2'];
var xScale = d3.scale.ordinal()
.domain(d3.range(chartdata.length))
.rangeRoundBands([0, w], 0.05);
var yScale = d3.scale.linear()
.domain([0, d3.max(chartdata)])
.range([0, h]);
var svg = d3.select(".plot-div")
.append("svg")
.attr("preserveAspectRatio", "xMidYMin")
.attr("viewBox", "0 0 1000 650")
.attr("width", "100%")
.attr("height", "100%")
.attr("display",'block')
.append("g").attr("class","stackChart2")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Create bars
svg.selectAll("barRect")
.data(chartdata)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return yScale(d);
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
})
.on("click", function() {
sortBars();
});
//Create labels
svg.selectAll("text")
.data(chartdata)
.enter()
.append("text")
.text(function(d) {
return d;
})
.attr("text-anchor", "middle")
.attr("x", function(d, i) {
return xScale(i) + xScale.rangeBand() / 2;
})
.attr("y", function(d) {
return h - yScale(d) - 10;
})
.attr("font-family", "sans-serif")
.attr("font-size", "14px")
.attr("fill", "black");
//Text Label
var yTextPadding = 20;
svg.selectAll(".bartext2")
.data(textData)
.enter()
.append("text")
.attr("class", "bartext2")
.attr("text-anchor", "middle")
.attr("fill", "black")
.attr("font-family", "sans-serif")
.style("font-size", "16px")
.attr("x", function(d,i) {return xScale(i)+xScale.rangeBand()/2;})
.attr("y", 460)
.text(function(d){return d;});
//Define sort order flag
var sortOrder = false;
//Define sort function
var sortBars = function() {
//Flip value of sortOrder
sortOrder = !sortOrder;
svg.selectAll("rect")
.sort(function(a, b) {
if (sortOrder) {
return d3.ascending(a, b);
} else {
return d3.descending(a, b);
}
})
.transition()
.delay(function(d, i) {
return i * 50;
})
.duration(1000)
.attr("x", function(d, i) {
return xScale(i);
});
}
});//End of D3 function

Can't get the right output for labels to d3 line graph

I am new to D3 and I am trying to replicate the D3 Multi-Series Line Chart example with a different data format. Instead of "Month/Year" format for the x-axis, I have "Year" only. But I am unable to get the labels to show up correctly, instead of 2012, 2013 etc. I get .012, .013 etc.
I tried different things with parsing the date and looked around for solutions, but I can't figure out how to get the right output.
Then another thing is that I would like to start the line one tick to the right, so 2012 starts at the position of 2013, but I can't find out how to do this.
Can anyone help me to solve this? Many thanks in advance!
This is the code:
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
body { font: 12px Arial;}
/* opmaak linechart*/
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.legend {
font-size: 10px;
/*font-weight: bold;*/
text-anchor: middle;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 40, right: 50, bottom: 50, left: 75},
width = 500 - margin.left - margin.right,
height = 275 - margin.top - margin.bottom;
// Parse the date / time
//var parseDate = d3.timeParse("%y");
//var parseDate = d3.time.format("%Y");
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var chartOmzet = d3.select("body").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 + ")");
var omzetline = d3.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.rev); });
d3.csv("rev.csv", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
//d.year = parseDate(d.year);
//d.year = +d.year;
d.rev = +d.rev;
});
x.domain(d3.extent(data, function(d) { return d.year; }));
y.domain([0, d3.max(data, function(d) { return d.rev; })]);
var dataNest = d3.nest()
.key(function(d) {return d.symbol;})
.entries(data);
var color = d3.scaleOrdinal(d3.schemeCategory10);
legendSpace = width/dataNest.length; // spacing for the legend */
dataNest.forEach(function(d,i) {
chartOmzet.append("path")
.attr("class", "line")
.style("stroke", function() { // Add the colours dynamically
return d.color = color(d.key); })
.attr("d", omzetline(d.values));
chartOmzet.append("text")
.attr("x", (legendSpace/2)+i*legendSpace) // space legend
.attr("y", height + (margin.bottom/2)+ 15)
.attr("class", "legend") // style the legend
.style("fill", function() { // Add the colours dynamically
return d.color = color(d.key); })
.text(d.key);
});
// Add the X Axis
chartOmzet.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
chartOmzet.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y));
// Add a small label for the graph name.
chartOmzet.append("text")
.attr("class", "title")
.attr("x", 10)
.attr("y", -10)
.attr("font-weight", "bold")
.style("text-anchor", "start")
.style("font-size", 10)
.text("Rev Activities");
});
</script>
</body>
And this is the data:
symbol,year,rev
Services,2012,14.720
Services,2013,19.452
Services,2014,28.804
Services,2015,46.598
Services,2016,54.173
Goods,2012,53.908
Goods,2013,58.709
Goods,2014,55.175
Goods,2015,59.331
Goods,2016,55.985
And this is the output:
You have some problems:
You're not parsing the dates;
The specifier is wrong, it should be "Y", not "y";
Set a different name for the format function (which you don't need);
Use .ticks(d3.timeYear)); to show just the year in the axis, and just one tick per year.
Here is the code with those changes:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
/* set the CSS */
body {
font: 12px Arial;
}
/* opmaak linechart*/
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.legend {
font-size: 10px;
/*font-weight: bold;*/
text-anchor: middle;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var csv = `symbol,year,rev
Services,2012,14.720
Services,2013,19.452
Services,2014,28.804
Services,2015,46.598
Services,2016,54.173
Goods,2012,53.908
Goods,2013,58.709
Goods,2014,55.175
Goods,2015,59.331
Goods,2016,55.985`;
var margin = {
top: 40,
right: 50,
bottom: 50,
left: 75
},
width = 500 - margin.left - margin.right,
height = 275 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.timeParse("%Y");
var formatDate = d3.timeFormat("%Y");
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var chartOmzet = d3.select("body").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 + ")");
var omzetline = d3.line()
.x(function(d) {
return x(d.year);
})
.y(function(d) {
return y(d.rev);
});
var data = d3.csvParse(csv);
data.forEach(function(d) {
d.year = parseDate(d.year);
//d.year = +d.year;
d.rev = +d.rev;
});
x.domain(d3.extent(data, function(d) {
return d.year;
}));
y.domain([0, d3.max(data, function(d) {
return d.rev;
})]);
var dataNest = d3.nest()
.key(function(d) {
return d.symbol;
})
.entries(data);
var color = d3.scaleOrdinal(d3.schemeCategory10);
legendSpace = width / dataNest.length; // spacing for the legend */
dataNest.forEach(function(d, i) {
chartOmzet.append("path")
.attr("class", "line")
.style("stroke", function() { // Add the colours dynamically
return d.color = color(d.key);
})
.attr("d", omzetline(d.values));
chartOmzet.append("text")
.attr("x", (legendSpace / 2) + i * legendSpace) // space legend
.attr("y", height + (margin.bottom / 2) + 15)
.attr("class", "legend") // style the legend
.style("fill", function() { // Add the colours dynamically
return d.color = color(d.key);
})
.text(d.key);
});
// Add the X Axis
chartOmzet.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.ticks(d3.timeYear));
// Add the Y Axis
chartOmzet.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y));
// Add a small label for the graph name.
chartOmzet.append("text")
.attr("class", "title")
.attr("x", 10)
.attr("y", -10)
.attr("font-weight", "bold")
.style("text-anchor", "start")
.style("font-size", 10)
.text("Rev Activities");
</script>
</body>

D3 linegraph using JSON data from API

I am working on making a linegraph in D3 that displays data in JSON format that is retrieved from an API.
I found an example line graph project on D3's website and it seems relatively straight forward. The main difference between the example and my project is that the example uses data from a local csv file instead of JSON from an API request.
// This needs to be changed to my API request
d3.tsv("data.tsv", function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
return d;
}, function(error, data) {
if (error) throw error;
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.select(".domain")
.remove();
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Price ($)");
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
});
I changed the csv request part to this because I'm trying to load in my JSON from the API:
d3.json(url).get(function(d) {
for(var i = 0; i < d.Data.length; i++) {
coinValue.push(d.Data[i].close);
dates.push(convertUnixTime(d.Data[i].time));
}
return d;
}, function(error, data) {
if (error) throw error;
This isn't working. I'm confused about why there's a comma and then another function right after.
What is the proper way to make the exact same line graph but with a d3.json function/API request instead?
My JSON looks like this: https://min-api.cryptocompare.com/data/histoday?fsym=ETH&tsym=USD&limit=2&aggregate=3&e=CCCAGG
The difference between d3.csv and d3.json is the accessor (2nd argument).
d3.csv: d3.csv(url[[, row], callback]) in which row is nothing but each row of the data fetched i.e. if you add a function as follows:
d3.csv(url, function(d) {
// each row of the data
}, callback)
d3.json: d3.json(url[, callback]) DOES NOT provide this each row accessor. But you can do that within the callback as follows:
d3.json(url, function(err, data) {
data.forEach(function(row) {
// parse each row as required
});
})
Using the above syntax along with the provided example code and JSON URL, here's a code snippet drawing a line chart:
// This needs to be changed to my API request
d3.json("https://min-api.cryptocompare.com/data/histoday?fsym=ETH&tsym=USD&limit=2&aggregate=3&e=CCCAGG", function(error, d) {
var data = d.Data;
data.forEach(function(d){ d.time = new Date(d.time * 1000) });
//console.log(data);
if (error) throw error;
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 50},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%d-%b-%y");
var x = d3.scaleTime()
.rangeRound([0, width]);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var line = d3.line()
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.close); });
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.select(".domain")
.remove();
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Price ($)");
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
});
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="500"></svg>
Hope this helps.

D3.js Tree:How to change the node color when clicking on it?

I want to change the color of nodes when clicking on it. It should be set in the "click" function.
However, in the click function, "d" is the node element, but to change node color I have to select the "circle" element, which is append to node using nodeEnter.append("circle"). How can I achieve it?
the html file:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 30px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 7px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 20, left: 20},
width = 1200 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree();
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("parse_tree.json", function(error, parse_tree) {
if (error) throw error;
root = parse_tree;
root.x0 = height / 2;
root.y0 = 0;
tree.size([width, height]);
update(root);
});
function update(source1) {
var originalConsole = console;
var nodes = tree.nodes(root);
var links = tree.links(nodes);
nodes.forEach(function(d) { d.y = d.depth * 100; });
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source1.y0 + "," + source1.x0 + ")"; })
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("text")
.attr("x", function(d) { return d.children || d._children ? -20 : 20; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
nodeUpdate.select("circle")
.attr("r", 10)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source1.y + "," + source1.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id; }
);
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source1.x0, y: source1.y0};
return diagonal({source: o, target: o});
})
.style("stroke-width", "3px")
.style("stroke", "green");
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source1.x, y: source1.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
for(var k = 0; k < 1000; k++)
flag = 0;
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
</script>
the json file:
{"name":"VP",
"size":"89",
"children": [
{"name":"VBP",
"size":"15",
"children":[{"name":"are", "size":"38"}]
},
{"name":"NP",
"size":"83",
"children": [
{"name":"DT",
"size":"29",
"children":[{"name":"a", "size":"53"}]
},
{"name":"NN",
"size":"50",
"children":[{"name":"boy", "size":"99"}]
}
]
}
]
}
d is the data, not the node
this in the click function is the dom node
d3.select(this) is the selection that picks just that node as a d3 selection
so to get a child circle element in a selection, you would do
d3.select(this).select("circle")
Now you'd think you'd just add a style("fill", "red") or whatever to this and job done, but as this is the oft-forked example from Mike Bostock that style would get overridden again in the transition update. So what you need to do is set a property on the data and then get that picked up by the drawing in the enter/update/exit sequence.
In the end all you need in the click function is:
d.clicked = true;
and in the nodeUpdate section do this -->
nodeUpdate.select("circle")
.attr("r", function(d){ return computeRadius(d); })
.style("fill", function(d) { return d.clicked ? "red" : (d._children ? "lightsteelblue" : "#fff");});
Note this doesn't do anything about un-highlighting a node. (You'd have to toggle the previous d.clicked boolean value)

Adding text to links that is represented in a JSON list - D3.js tree layout

I have the following code , and i'm trying to append text to my links (i need to represent country names on top of the link), notice that the text is embedded within a json list, and that's why i think i cant get it into my links:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Collapsible Tree Example</title>
<style>
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text { font: 12px sans-serif; }
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
</style>
</head>
<body>
<!-- load the d3.js library -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var treeData = [
{"name":["1"],"weights":["17284"],"variableNames":["Country name"],"criteria":
{"levels":["","Brazil","Canada","France","Germany","Spain","Turkey","United Kingdom","United States"]},
"children":[{"name":["2"],"weights":["5920"],"variableNames":["Country name"],
"criteria":{"levels":["","Brazil","Canada","France","Germany","Spain","Turkey","United Kingdom","United States"]},
"children":[{"name":["3 "],"weights":["604"],"prediction":["0.256"]},{"name":["4 "],"weights":["5316"],"prediction":["0.231"]}],"prediction":["0.233"]},{"name":["5"],"weights":["11364"],
"variableNames":["Country name"],"criteria":{"levels":["","Brazil","Canada","France","Germany","Spain","Turkey","United Kingdom","United States"]},
"children":[{"name":["6 "],"weights":["10029"],"prediction":["0.153"]},{"name":["7 "],"weights":["1335"],
"prediction":["0.053"]}],"prediction":["0.141"]}],"prediction":["0.173"]}
];
// ************** Generate the tree diagram *****************
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Declare the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; });
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", "#fff");
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1);
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
link.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.source.criteria.levels; }) // also tried d.source.criteria
.style("fill-opacity", 1);
}
</script>
</body>
</html>
I tried to get help from other questions like this one: Adding text labels to force directed graph links in d3.js
But couldn't find any question that had an embedded list.
Thanks in Advance!!
The key is that you define the links setup just like the nodes, i.e. build a g that contains the path and the text. In your example, the text ends up in the path elements, respectively.
So this is key:
var linkG = link.enter()
.append('g')
.attr('class', 'link');
Here's a fiddle
(I changed a few bit here and there as well... not that I wasn't sure what texts you want to display)