Visualization Goal: Build a D3 Tree that has text at, both, nodes and links, and that transitions cleanly, when nodes are selected/deselected.
Problem: While I can get link text, called "predicates," to show up along the centroid of all link paths, I can't seem to get them to transition in and out "smoothly."
Question: Can someone please help me please help me clean up the code and better understand how tree "link" transitions are behaving so I understand the theory behind the code?
Visualization and Source Location: http://bl.ocks.org/Guerino1/raw/ed80661daf8e5fa89b85/
The existing code looks as follows...
var linkTextItems = vis.selectAll("g.linkText")
.data(tree.links(nodes), function(d) { return d.target.id; })
var linkTextEnter = linkTextItems.enter().append("svg:g")
.attr("class", "linkText")
.attr("transform", function(d) { return "translate(" + (d.target.y + 20) + "," + (getCenterX(d)) + ")"; });
// Add Predicate text to each link path
linkTextEnter.append("svg:foreignObject")
.attr("width", "120")
.attr("height", "40")
.append("xhtml:body")
.attr("xmlns", "http://www.w3.org/1999/xhtml")
.html(function(d){ return "<p>" + (linksByIdHash[d.source.id + ":" + d.target.id].predicate) + "</p>"; });
// Transition nodes to their new position.
//var linkTextUpdate = linkTextItems.transition()
//.duration(duration)
//.attr("transform", function(d) { return "translate(" + d.source.x + "," + d.source.y + ")"; })
//linkTextUpdate.select("linkText")
//.style("fill-opacity", 1);
// Transition exiting linkText to the new position of the parents.
var linkTextExit = linkTextItems.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.source.y + 20 + "," + (getCenterX(d)) + ")"; })
.remove();
linkTextExit.select("linkText")
.style("fill-opacity", 1e-6);
function getCenterX(d) {
var xS = d.source.x;
var xT = d.target.x;
if(xS == xT)
{ return (xS - (xS - xT)/2); }
else if(xS > xT)
{return (xS - (xS - xT)/2); }
else
{ return (xT - (xT - xS)/2); }
}
Some Symptoms...
When link text transitions in or out, it's choppy / not smooth
When a branch is collapse, link text doesn't transition to appropriate path centroids
My frustration is that I feel like I'm very close but that I'm missing something very simple/basic. Any help is greatly appreciate.
Related
I just have a quick question regarding changing the color of a path of a stroke of the stock radial tree by Mike Bostock
https://bl.ocks.org/mbostock/4063550
For example, if I can change the color of sub links such as:
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.style("fill", "none")
.attr("d", function(d) {
return "M" + project(d.x, d.y)
+ "C" + project(d.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, d.parent.y);
});
link.attr('stroke', function(d) {
if (d.id.startsWith("Root.Item1")){
return "#386eff";
}
if (d.id.startsWith("Root.Item2")){
return "#45cbf2";
}
else return '#70f2ad';
});
This will change all the link colors for data that starts with Root.Item2
I.e. Root.Item2.Child1 and Root.Item2.Child2
will have the same color.
However, what If I wish to highlight the paths for Root.Item2.Child2 only and leave the other links the same color?
The concept is something like highlight the path that starts with Root and ends in Child2?
Thanks
I was able to figure this out in a roundabout way by checking the d.children of the node. Not sure if it's ideal but it works if anyone else wishes to do something similar.
if (d.id.startsWith("Root.Item2")) {
for (var i = 0; i < d.children.length; i++ ) {
if (d.id.startsWith("Root.Item2.Child1") |
) {
return "red";
}
}
I have a zoom function which works perfectly fine for circles, but not for labels.
svgEnter.attr('transform', 'translate(' + d3.event.translate + ')scale(' + d3.event.scale + ')');
How to "attach" labels to circle, so it follows parent when users scroll or zoom?
Here is an example fiddle: http://jsfiddle.net/seveneleven/z4m5c06a/1/
In case if anyone wondering: plain text nodes is the way to go.
var ne = node.enter()
.append('a')
.attr('class', function(d) {
return d.style + '-bubble-node';
})
.attr('alt', function(d) {
return '#' + (encodeURIComponent(idValue(d)));
})
.call(force.drag)
.call(connectEvents);
ne.append('text')
.text(function(d) {
return d.name
});
ne.append('circle')
.attr('r', function(d) {
return rScale(rValue(d));
});
I adapted Ger Hobbelt's excellent example of group/bundle nodes
https://gist.github.com/GerHobbelt/3071239
as a JSFiddle here:
https://jsfiddle.net/NovasTaylor/tco2fkad/
The display demonstrates both collapsible nodes and regions (hulls).
The one tweak that eludes me is how to add labels to expanded nodes. I have successfully added labels to nodes in my other force network diagrams using code similar to:
nodes.append("text")
.attr("class", "nodetext")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
// d.name is a label for the node, present in the JSON source
return d.name;
});
Is anyone familiar enough with Ger's example to guide me in the right direction?
On enter, instead of appending circle append a g with a circle and a text. Then re-factor a bit to fix the movement of the g instead of the circle. Finally, append write out the .text() only if the node has a name (meaning it's a leaf):
node = nodeg.selectAll("g.node").data(net.nodes, nodeid);
node.exit().remove();
var onEnter = node.enter();
var g = onEnter
.append("g")
.attr("class", function(d) { return "node" + (d.size?"":" leaf"); })
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
g.append("circle")
// if (d.size) -- d.size > 0 when d is a group node.
.attr("r", function(d) { return d.size ? d.size + dr : dr+1; })
.style("fill", function(d) { return fill(d.group); })
.on("click", function(d) {
expand[d.group] = !expand[d.group];
init();
});
g.append("text")
.attr("fill","black")
.text(function(d,i){
if (d['name']){
return d['name'];
}
});
And refactored tick to use g instead of circle:
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
Updated fiddle.
I am facing a problem trying to position text inside the wedges of a Sunburst chart which is based on d3.js.The text elements seem to be not positioned as desired even on zooming..
Here is the brief snippet of the code that i tried, but unsuccessfully :
var slices = svg.selectAll(".form")
.data(function(d) { return data_slices; })
.enter()
.append("g");
slices.append("path")
.attr("d", arc)
.attr("id",function(d,i){return d[2]+""+i;})
.style("fill", function(d) { return color(d[2]);})
.on("click",animate)
.attr("class","form")
.append("svg:title")
.text(function(d) { return Math.round(d[0]*100)/100 +" , "+ Math.round(d[1]*100)/100; });
//Something needs to change below....
slices.append("text")
.style("font-size", "10px")
.attr("x", function(d) { return y(d[1]); })
.attr("transform", function(d) { return "rotate(" + this.parentNode.getBBox().width + ")"; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d){return d[2]})
.attr("pointer-events","none");
Here is the Fiddle of the chart Fiddle
What can be possible problem ? and can anyone please tell me or guide me as to how to position the <text> inside svg <path>.Looks like the solution is a minor tweak to this, but i am not able to get to it even after trying for a long time..
Any help/comment in the direction of a solution would be greatly appreciated...Thanks in Advance..
I think this comes close to what you aimed to achieve: http://jsfiddle.net/4PS53/3/
The changes needed are the following:
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;
}
slices.append("text")
.style("font-size", "10px")
.attr("x", function(d) { return d[1]; })
// Rotate around the center of the text, not the bottom left corner
.attr("text-anchor", "middle")
// First translate to the desired point and set the rotation
// Not sure what the intent of using this.parentNode.getBBox().width was here (?)
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")" + "rotate(" + getAngle(d) + ")"; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d){return d[2]})
.attr("pointer-events","none");
Now to make it work with zooming, where the reference point changes, we need a bit more infrastructure.
Make the g.form-container and not only the path.form visible/invisible. This means that we do not have to worry about making the labels disappear separately. (I have added the form-container class.)
Calculate the new point and calculate the centroid and rotation for it. This is a bit more tricky, but not too difficult:
function change_ref(data_point, reference_point) {
return [
get_start_angle(data_point, reference_point),
get_stop_angle (data_point, reference_point),
data_point[2],
get_level (data_point, reference_point)
];
}
// And while doing transitioning the `text.label`:
svg.selectAll('.label')
.filter(
function (b)
{
return b[0] >= new_ref[0] && b[1] <= new_ref[1] && b[3] >= new_ref[3];
}
).transition().duration(1000)
.attr("transform", function(b) {
var b_prime = change_ref(b, d);
return "translate(" + arc.centroid(b_prime) + ")" +
"rotate(" + getAngle(b_prime) + ")";
})
I have added the class label to the text.
Updated Demo: http://jsfiddle.net/4PS53/6/
However, I have argued that there might be better ways of presenting this data, esp. if you are allowing zooming and panning: D3 put arc labels in a Pie Chart if there is enough space
I've made a little improve in musically_ut code.
Now you can change from one data to another.
$('#change').click(function () {
if (animating) {
return;
}
if (currentSet == 0) {
currentSet = 1;
svg.selectAll(".form").filter(
function (d) {
return d[0] >= ref[0] && d[1] <= ref[1] && d[level_index] >= ref[level_index];
}
)
.transition().duration(1000)
.attrTween("d", changeDatarebaseTween(0, 1, 2, 3));
svg.selectAll('.label').filter(
function (d) {
return d[0] >= ref[0] && d[1] <= ref[1] && d[level_index] >= ref[level_index];
}
)
.transition().duration(1000)
.attr("transform", function (b) {
var b_prime = change_ref_CD(b);
return "translate(" + arc.centroid(b_prime) + ")" +
"rotate(" + getAngle(b_prime) + ")";
})
}
else {
currentSet = 0;
svg.selectAll(".form").filter(
function (d) {
return d[2] >= ref[2] && d[3] <= ref[3] && d[level_index] >= ref[level_index];
}
)
.transition().duration(1000).attrTween("d", changeDatarebaseTween(2, 3, 0, 1));
svg.selectAll('.label').filter(
function (d) {
return d[2] >= ref[2] && d[3] <= ref[3] && d[level_index] >= ref[level_index];
}
)
.transition().duration(1000)
.attr("transform", function (b) {
var b_prime = change_ref_CD(b);
return "translate(" + arc.centroid(b_prime) + ")" +
"rotate(" + getAngle(b_prime) + ")";
})
}
setTimeout(function () {
animating = false;
}, 1000);
});
EDIT: http://jsfiddle.net/k1031ogo/3/
(code could be cleaner, too much copy/paste)
I have the following WordCloud.
The original Code is from Jason Davies D3-JavaScript-WordCloud Example.
https://github.com/jasondavies/d3-cloud/blob/master/examples/simple.html
Most of the Code below is from this really helpfull tutorial (in German):
http://www.advitum.de/blog/2012/04/tagcloud-mit-php-und-javascript-erstellen-word-cloud-d3/
Thank You Lars Ebert!
EDIT: Now I have learned a little bit more. I have cleared nonsensical code.
My goal is to center the first word from array horizontally.
The first word is now centered horizontally, but now there is a gap in the Cloud.
Just at the x,y point where the word would be without positioning.
My new question is: How do I remove this gap?
Thank You.
var wordcloud, size = [800, 800]; //Cloud Size
var fillColor = d3.scale.category20b();
function loaded() {
d3.layout.cloud()
.size(size)
.words(words)
.font("Impact")
.fontSize(function(d) { return d.size;})
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.on("end", draw)
.start();
}
function draw(words) {
wordcloud = d3.select("body")
.append("svg")
.attr("width", size[0])
.attr("height", size[1])
.append("g")
.attr("transform", "translate(" + (size[0]/2) + "," + (size[1]/2) + ")");
wordcloud.selectAll("text")
.data(words)
.enter()
.append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("fill", function(d) { return fillColor(d.text.toLowerCase()); })
.attr("text-anchor", "middle")
//Edit
.attr("transform", function(d, i) {
if(i == 0){
return "translate(" + [0, 0] + ")rotate(" + 0 + ")"; //handle first element
}else{
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")"; //handle the rest
}
})
//--------------
.text(function(d) { return d.text; });
}
Use a negative margin equal to one of the size calculations:
wordcloud.selectAll("text")
...
...
...
.style("margin-left", function(d) {return d.size + "px"})