I'm attempting to modify the D3 sunburst example here (http://bl.ocks.org/mbostock/4063423) to add labels to the arcs.
I've added code to compute the angle and generate the text (taken from this example: http://jsfiddle.net/4PS53/6/) but it's not working. The text just doesn't show up, and when I try to inspect the <text> elements using the inspector, they appear not to be in the DOM.
Any help is much appreciated! The JS code is below (modified from the original D3 gallery example), the JSON data is the same. I'm showing the parts of the code that I modified; the rest is the same.
d3.json("data.json", function(error, root) {
console.log(root);
var path = svg.datum(root).selectAll("path")
.data(partition.nodes)
.enter().append("path")
.attr("display", function(d) { return d.depth ? null : "none"; }) // hide inner ring
.attr("d", arc)
.style("stroke", "#fff")
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.style("fill-rule", "evenodd")
.each(stash);
path.append("text")
.text(function(d) { return d.name})
.classed("label", true)
.attr("x", function(d) { return d.x; })
.attr("text-anchor", "middle")
// translate to the desired point and set the rotation
.attr("transform", function(d) {
if (d.depth > 0) {
return "translate(" + arc.centroid(d) + ")" +
"rotate(" + getAngle(d) + ")";
} else {
return null;
}
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("pointer-events", "none");
});
function getAngle(d) {
// Offset the angle by 90 deg since the '0' degree axis for arc is Y axis, while
// for text it is the X axis.
var thetaDeg = (180 / Math.PI * (arc.startAngle()(d) + arc.endAngle()(d)) / 2 - 90);
// If we are rotating the text by more than 90 deg, then "flip" it.
// This is why "text-anchor", "middle" is important, otherwise, this "flip" would
// a little harder.
return (thetaDeg > 90) ? thetaDeg - 180 : thetaDeg;
}
You need g group element to add text, so you can just change:
var path = svg.datum(root).selectAll("path")
.data(partition.nodes)
.enter().append("path")
.attr("display", function(d) { return d.depth ? null : "none"; }) // hide inner ring
.attr("d", arc)
.style("stroke", "#fff")
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.style("fill-rule", "evenodd")
.each(stash);
to
var path = svg.datum(root).selectAll("path")
.data(partition.nodes)
.enter().append("g")
path.append("path")
.attr("display", function(d) { return d.depth ? null : "none"; }) // hide inner ring
.attr("d", arc)
.style("stroke", "#fff")
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.style("fill-rule", "evenodd")
.each(stash);
Check this jsFiddle:
http://jsfiddle.net/5d0zd2s7/
Related
I'm trying to dynamically update the data on a hierarchical edge bundling chart based on https://bl.ocks.org/bstitt79.
I create an update function to wrap the behavior of the example above.
var links, nodes;
var link = svg.selectAll(".link")
var node = svg.selectAll(".node");
function update(root){
links = packageImports(root.descendants());
cluster(root);
nodes = root.descendants();
link = link
.data(links)
.enter()
.append("path")
.each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })
.attr("class", "link")
.attr("d", line)
.style("stroke", function(d) {
var splitName = d.source.data.name.split(".");
var group = splitName[0] + '.' + splitName[1];
return color(group);
});
link
.exit()
.transition()
.attr('r', 0)
.remove();
node = node
.data(nodes.filter(function(n) { return !n.children; }))
.enter().append("text")
.attr("class", "node")
.attr("dy", ".31em")
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" + (d.x < 180 ? "" : "rotate(180)"); })
.style("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.text(function(d) { return d.data.key; });
}
now let's assume that I have two json sources, (flare1.json, flare2.json) with different data inside.
The default datasource is flare1, after 5000ms I want to change the data to flare2, so I tried with a timeout like this.
d3.timeout(function() {
root = packageHierarchy(jsonData);
update(root);
}, 5000);
But this isn't working as expected, because the result is a chart with all the overlapping data from flare1 and flare2.
First Datasource
Result After 5000ms timeout
What i'm doing wrong? can someone help?
I'm splitting data into categories e.g. rich poor and all. Using dropdown to get those values to display on a scatterplot. First transition happens, everything works as expected. Text labels are correctly displayed too, however when another option is selected and second transition happened half of circles are disappearing and every other transition is messed up. Only works if option all selected than again, first transition works, after that it is all messed up.
Codepen
function render(someData) {
xScale
.domain([
d3.min(someData, function(d) {
return +d.poorToys;
}),
d3.max(someData, function(d) {
return +d.poorToys;
})
]);
yScale
.domain([
d3.min(someData, function(d) {
return +d.richToys;
}),
d3.max(someData, function(d) {
return +d.richToys;
})+ 20
]);
//Adding circles
var circles = svg.selectAll("circle")
.data(someData, function(d) {
return d.country;
});
I believe a problem starts here.
circles
.enter()
.append("circle")
.attr("cx", function(d) {
if (currentSelection === "rich") {
return width - margin.right;
} else if (currentSelection === "poor") {
return margin.left;
} else if (currentSelection === "all") {}
return xScale(+d.poorToys);
})
.attr("cy", function(d) {
if (currentSelection === "rich") {
return margin.top;
} else if (currentSelection === "poor") {
return height - margin.bottom;
} else if (currentSelection === "all") {}
return yScale(+d.richToys);
})
.attr("r", function(d) {
if (currentSelection === "all") {
return rad;
}
})
.style("fill", "red")
.append("title")
.text(function(d) {
return d.country + " reports books for " + d.poorToys + "% in poor areas and " + d.richToys + "% in rich areas.";
});
circles
.transition()
.duration(2000)
.attr("cx", function(d) {
return xScale(+d.poorToys);
})
.attr("cy", function(d) {
return yScale(+d.richToys);
})
.attr("r", function() {
if (currentSelection !== "all") {
return rad * 1.5;
} else {
return rad;
}
});
circles
.exit()
.transition()
.duration(1000)
.style("opacity", 0)
.remove();
//Update x axis
svg.select(".x.axis")
.transition()
.duration(1000)
.call(xAxis);
//Update y axis
svg.select(".y.axis")
.transition()
.duration(1000)
.call(yAxis);
if (currentSelection !== "all"){
var labels = svg.selectAll("text.labels")
.data(someData, function(d){
return d.country;
});
labels
.enter()
.append("text")
.attr("transform", function(d){
return "translate(" + xScale(+d.poorToys) + "," + yScale(+d.richToys) + ")";
})
.attr("dx", 2)
.attr("dy", 1)
.attr("class", "labels")
.style("fill", "white")
.style("font-size", "5px")
.text(function(d){
return d.country;
});
labels
.transition()
.duration(2000)
.style("opacity", 1);
labels
.exit()
.remove();
} else {
svg.selectAll("text.labels")
.transition()
.duration(1000)
.style("opacity", 0)
.remove();
}
}
You incorrectly give your x axis a class of x_axis on line 57 then later try to select it as x.axis in your render function on line 179.
Once you fix that up, I think it should work as expected.
svg
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + -14 + "," + (height + 30) + ")")
.call(xAxis);
Updated Pen
Apart from the axis problem found by #ksav your main problem is that you don't position the labels. Many labels are present in rich and poor.
var labels = svg.selectAll("text.labels")
.data(someData, function(d){ return d.country; });
labels
.enter()
.append("text")
.attr("x", function(d){ return xScale(+d.poorToys); })
.attr("y", function(d){ return yScale(+d.richToys); })
.attr("dx", 2)
.attr("dy", 1)
.attr("class", "labels")
.attr("opacity", 0)
.style("fill", "white")
.style("font-size", "8px")
.text(function(d){ return d.country; })
.merge(labels)
.transition()
.duration(2000)
.attr("x", function(d){ return xScale(+d.poorToys); })
.attr("y", function(d){ return yScale(+d.richToys); })
.attr("opacity", 1);
Also don't position the circles based on the selection
circles
.enter()
.append("circle")
.attr("cx", function(d) { return xScale(+d.poorToys); })
.attr("cy", function(d) { return yScale(+d.richToys); })
.attr("r", function(d) { return rad; })
.style("fill", "red")
.append("title")
.text(function(d) {
return d.country + " reports books for " + d.poorToys + "% in poor areas and " + d.richToys + "% in rich areas.";
});
I have a JSON file which looks like this:
{
"name":"TOPICS",
"children":[
{
"name":"Topic A",
"children":[
{
"name":"Anna",
"size":5,
"text":"A story",
"sentiment":0.8,
"source":"dictionary",
"color":"gray"
},
This is fed into a D3 chart which reads this file. What I am trying to do is like the existing fill function below:
.style("fill", function (d) { return color((d.children ? d : d.parent).data.name); });
To instead pull the colour from the json. In this case, gray. Can anyone please help me?
EDIT: Based on the help below I have this code but it still does not work:
g.selectAll('g')
.data(root.descendants())
.enter().append('g').attr("class", "node").append('path')
.attr("display", function (d) { return d.depth ? null : "none"; })
.attr("d", arc)
.style('stroke', '#fff')
.style("fill", d => (d.children? d: d.parent).data.color);
// Populate the <text> elements with our data-driven titles.
g.selectAll(".node")
.append("text")
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")rotate(" + computeTextRotation(d) + ")"; })
.attr("dx", "-20") // radius margin
.attr("dy", ".5em") // rotation align
.text(function(d) { return d.parent ? d.data.name : "" });
});
I am creating a sankey diagram using D3. I am trying to redraw the diagram with additional node and link and using transition to animate the previous diagram to the new diagram. I was able to add in new node and link but the old nodes and links did not change position. Since the new node and link could be added at any place within the diagram, I do not want to clear and redraw the entire svg, but use transition to get from the old diagram to the new one. The code to draw the sankey diagram is this:
function draw(data){
// Set the sankey diagram properties
var sankey = d3sankey()
.nodeWidth(17)
.nodePadding(27)
.size([width, height]);
var path = sankey.link();
var graph = data;
sankey.nodes(graph.nodes)
.links(graph.links)
.layout(32);
sankey.relayout();
// add in the links
link.selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("fill", "none")
.style("stroke", function(d){
return "grey";
})
.style("stroke-opacity", "0.4")
.on("mouseover", function() { d3.select(this).style("stroke-opacity", "0.7") } )
.on("mouseout", function() { d3.select(this).style("stroke-opacity", "0.4") } )
.style("stroke-width", function (d) {
return Math.max(1, d.dy);
})
.sort(function (a, b) {
return b.dy - a.dy;
});
link.transition().duration(750);
//link.exit();
// add in the nodes
var node = nodes.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
// add the rectangles for the nodes
node.append("rect")
.attr("height", function (d) {
return d.dy;
})
.attr("width", sankey.nodeWidth())
.style("fill", function (d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
.style("fill-opacity", ".9")
.style("shape-rendering", "crispEdges")
.style("stroke", function (d) {
return d3.rgb(d.color).darker(2);
})
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function (d) {
return d.dy / 2;
})
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("text-shadow", "0 1px 0 #fff")
.attr("transform", null)
.text(function (d) {
return d.name;
})
.filter(function (d) {
return d.x < width / 2;
})
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
node.transition().duration(750);
}
The JSFiddle
Is it possible to use transition to add in new node and link and reposition
old nodes and links?
Thanks!
I was able to do this by using moving the nodes and links to new position. The code for that is:
var nodes = d3.selectAll(".node")
.transition().duration(750)
.attr('opacity', 1.0)
.attr("transform", function (d) {
if(d.node == 3){
console.log(d.x, d.y);
}
return "translate(" + d.x + "," + d.y + ")";
});
var nodeRects = d3.selectAll(".node rect")
.attr("height", function (d) {
if(d.node == 3){
console.log(d.dy);
}
return d.dy;
})
var links = d3.selectAll(".link")
.transition().duration(750)
.attr('d', path)
.attr('opacity', 1.0)
Updated JSFiddle
I suspect I'm barking up the wrong tree - although I've got no errors in the console to help me out.
I've got this block of code:
var path = svg.data([json]).selectAll("path")
.data(partition.nodes)
.enter()
.append("path")
.attr("display", function(d) {
return d.depth ? null : "none";
})
.attr("d", arc)
.style("stroke", "#fff")
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.attr("fill-rule", "evenodd")
.style("opacity", 1)
.each(stash);
//>>this section functions ok
path.append("title")
.text(function(d) {
return d.name + ": " + d.amount;
});
//>>this section throws no errors but "foo" does not appear
path.append("text")
.text(function(d) {
return "foo";
})
.style("stroke", "#3b5998")
.style("fill", "#3b5998");
The code snippet beginning path.append("title")... works ok but the final snippet beginning path.append("text")... adds the text foo to the html but it is not visible on the webpage - why is it not visible and how do I add labels?
This is part of this visual:
http://plnkr.co/edit/q9ODd5?p=preview
text cannot be nested with a path. You'll need to add it to the svg instead. Additionally, you'll want to position the text in some way:
svg.append("text")
.text(function(d) {
return "foo";
})
.attr("x", function(){ return 0 })
.attr("y", function(){ return 0 })
.style("stroke", "#3b5998")
.style("fill", "#3b5998");
The working code ended as the following:
var path = svg.data([json]).selectAll(".theArc")
.data(partition.nodes)
.enter()
.append("path")
.attr("class", "theArc") //<<<<<<<<<<new
.attr("id", function(d,i) { return "theArc_"+i; }) //Give each slice a unique ID //<<<<<<<<<<new jq
.attr("display", function(d) {
return d.depth ? null : "none";
})
.attr("d", arc)
.style("stroke", "#fff")
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.attr("fill-rule", "evenodd")
.style("opacity", 1)
.each(stash);
path
.append("title") //mouseover title showing the figures
.text(function(d) {
return d.name + ": " + d.amount;
});
svg.data([json]).selectAll(".theTxts")
.data(partition.nodes)
.enter()
.append("text")
.attr("class", "theTxts")
.attr("dx", 10) //Move the text from the start angle of the arc
.attr("dy", 18) //Move the text down
.append("textPath")
.attr("xlink:href", function(d, i) {
return "#theArc_" + i;
})
.text(function(d) {
return d.name;
})
.style("stroke", "#3b5998")
.style("fill", "#3b5998");