Rotate D3 SVG to line up to specific angle - javascript

I'm working with D3 to create a edge hierarchical model of some data.
What I want to be able to do is click on a edge node and have it line up with the red line.
What I've tried so far is trying to calculate the angle difference between the (x,y) locations of the middle, and the (x,y) locations of the edge node. This didn't give me the right results.
I think the right way to to this to get the angle of a node (in relation to the middle). Though I'm having a lot of trouble doing this since I cannot find which property stores this information. The edge hierarchy is based off this one:
http://bl.ocks.org/mbostock/7607999
The text is generated with the following piece of code:
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.key; })
.on("mouseover", mouseovered)
.on("mouseout", mouseouted);
Any information would be greatly helpful. Thanks

The line taking care of rotating the whole wheel is the following:
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" + (d.x < 180 ? "" : "rotate(180)"); })
The data is aligned with the red line when d.x=270. In your case, calling "origin" the selected piece of data (the one that must be on the red line), you need to give an angle of d.x - origin.x + 270.
To keep values between 0 and 360, the following trick works:
(d.x - origin.x + 270 +360) % 360
So the easiest way in my opinion is to add an "angle" field in your data, and then use it in lieu of d.x.
node = node
.data(nodes.filter(function(n) { return !n.children; }))
.enter().append("text")
.attr("class", "node")
.attr("dy", ".31em")
.each(function(d) { d.angle = (d.x - origin.x + 270 +360) % 360 })
.attr("transform", function(d) { return "rotate(" + d.angle - 90 + ")translate(" + (d.y + 8) + ",0)" + (d.angle < 180 ? "" : "rotate(180)"); })
.style("text-anchor", function(d) { return d.angle < 180 ? "start" : "end"; })
.text(function(d) { return d.key; })
.on("mouseover", mouseovered)
.on("mouseout", mouseouted);

I think the right way to to this to get the angle of a node (in relation to the middle)
You already have the angle of the node, maybe it has been confusing since you are assigning the rotation twice. But it can be unified into one by doing the following:
.attr("transform", function(d) {
var r = d.x < 180 ? d.x - 90 : (d.x - 90) + 180;
return "rotate(" + r + ")translate(" + (d.y + 8) + ",0)";
})
r will be the angle, but there are potential "problems" (or just makes the animation code a bit more cumbersome) with this version because some r values will be negative.
A better approach to avoid that is to translate your Y Axis and not the X Axis as you are doing now.
.attr("transform", function(d) {
// Simpler version for assigning the r value and avoid negatives.
var r = d.x > 180 ? d.x + 180 : d.x;
// Notice the negative value for the Y Axis.
return "rotate(" + r + ")translate(0, -" + (d.y + 8) + ")";
})
Now you have the nodes angles in relation to the center as you wanted.
Extra note to help you figure how to align it. Your red line is in the angle 180. So the way to rotate your graphic is to find the difference between r and 180.

Related

How do I change the position of a circle svg element using the transform attribute?

I am currently building a sunburst chart in D3JS and am trying to append circles to each node. You can view current project here: https://jsfiddle.net/mhxuo260/.
I am trying to position each of the circles in the top right hand corner of their respective node. Currently they will only position in the center which covers the node labels. I have been scouring the net in search for a clue but just haven't come up with anything yet. Any suggestion would be appreciated.
d3.json("flare.json", function(error, root) {
if (error) throw error;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
path = g.append("path")
.attr("d", arc)
.attr('stroke', 'white')
.attr("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", magnify)
.each(stash);
var text = g.append("text")
// .attr("x", function(d) { return d.x; })
// .attr("dx", "6") // margin
// .attr("dy", ".35em") // vertical-align
.text(function(d) {
return d.name;
})
.attr('font-size', function(d) {
return '10px';
})
.attr("text-anchor", "middle")
.attr("transform", function(d) {
if (d.depth > 0) {
return "translate(" + arc.centroid(d) + ")" +
"rotate(" + getStartAngle(d) + ")";
} else {
return null;
}
})
.on("click", magnify);
var circle = g.append('circle')
.attr('cx', function(d) { return d.x })
.attr('cy', function(d) { return d.dy; })
.attr('r', '10')
.attr('fill', 'white')
.attr('stroke', 'lightblue')
.attr("transform", function(d) {
console.log(arc.centroid(d))
if (d.depth > 0) {
return "translate(" + arc.centroid(d) + ")" +
"rotate(" + getStartAngle(d) + ")";
} else {
return null;
}
});
You are using the ´´´arc.centroid´´´ function which always returns the x,y midpoint of the arc. All that function is doing is:
The midpoint is defined as (startAngle + endAngle) / 2 and (innerRadius + outerRadius) / 2
You just need to calculate a different position using these values depending on where you want it. Use the transform like this (sudo code):
.attr( "transform", function(d) {
var x = (startAngle + endAngle) / 2;
var y = (innerRadius + outerRadius) / 2;
return "translate(" + x +"," + y + ")";
});
You don't need to rotate your circle.
(FYI: javascript will convert arrays into strings by joining each number with commas, this is why arc.centroid returning an array works here)

Rotation transformation of labels on x-axis

Hello I have this visual: https://plnkr.co/edit/H6M1xoS9cZv5dKCTIyid?p=preview
I'm attempting to rotate the labels of the x-axis and have tried amending the code in lines 299-317 to no avail - so I feel code such as .attr("transform", "rotate(-65)" ); needs adding to the following?
// Add x labels to chart
var xLabels = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + (margin.top + height) + ")");
xLabels.selectAll("text.xAxis")
.data(BarData)
.enter()
.append("text")
.text(function(d) {
return d.dt;
})
.attr("text-anchor", "middle")
// Set x position to the left edge of each bar plus half the bar width
.attr("x", function(d, i) {
return (i * (width / BarData.length)) + ((width / BarData.length - barPadding) / 2);
})
.attr("y", 15)
.attr("class", "xAxis")
Your issue is that you are using the .attr("x" to position the axis text. You should be using translate to do this otherwise your rotation will rotate all the elements from the bottom left.
the code should look like this, the translate and the rotation should occur within the same transform function:
.attr("transform", function (d, i) {
return "translate("
+ ((i * (width / BarData.length)) + ((width / BarData.length - barPadding) / 2))
+ ", 0) rotate(-65)";
})
Here is a working version of your plunk: https://plnkr.co/edit/UqwtLqTn6iJ2XS012Vr4?p=preview
Hope this helps.
With links help I eventually used the following code:
xLabels.selectAll("text.xAxis")
.data(BarData)
.enter()
.append("text")
.text(function(d) {
return d.dt;
})
.attr({
'text-anchor': "middle",
transform: function(d, i) {
var x = (i * (width / BarData.length)) + ((width / BarData.length - barPadding) / 2);
var y = 20;
return 'translate(' + x + ',' + y + ')rotate(-90)';
},
dy: "0.35em",
'class': "xAxis"
})
Working example here: https://plnkr.co/edit/3d5UhM?p=preview
I also added in the attribute dy: "0.35em" to fully centre the labels to each bar.

d3.js text rotate issue, numbers display reverse

I'm using d3 text to draw some numbers, and use rotate to change the position, but it seems it changes more than I expect, as in the screenshot, how to let the left side numbers reverse, I think it may like 3D rotate, I don't know how to solve it , or the text I draw is wrong.
g.selectAll('text')
.data(sumArr)
.enter()
.append('text')
.text(function(d){
return d;
})
.style('fill', '#aeaeae')
.attr('x', function(d){
console.log(d, x(d))
return x(d) + R + 10;
})
.attr('y', 12 * SCALE)
.attr('font-size', 12 * SCALE)
.attr('transform', function(d,i){
return 'rotate(' + (300/30 * i - 125) + ')';
});

How do I color labels for my pie chart in D3?

I started out with the following example:
http://jsfiddle.net/nrabinowitz/GQDUS/
I am trying to get the labels for each arc to be the color of the arc.
I have gotten it to where it colors all the labels the same color. But I do now know how to access each individual label and change the color.
In my code I have done the following for the last line:
arcs.append("svg:text").attr("transform", function (d){var c = arc.centroid(d); x = c[0]; y = c[1]; h = Math.sqrt(x*x + y*y); return "translate(" + (x/h * 100) + ',' + (y/h * 90) + ")";}).text(function(d){return Math.round((d.data/total)*100)+"%";}).attr("text-anchor","middle").attr("fill","color_data.pop()");
This makes all the labels the first color in my array. However I need each label to be a different color in the array. I am just not sure how to access the labels so I can loop through and change the color.
Just add the same fill argument that was used for the arcs e.g.
arcs.append("svg:text")
.attr("transform", function(d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x*x + y*y);
return "translate(" + (x/h * labelr) + ',' +
(y/h * labelr) + ")";
})
.attr("dy", ".35em")
.attr("fill", function(d, i) { return color(i); })
.attr("text-anchor", function(d) {
// are we past the center?
return (d.endAngle + d.startAngle)/2 > Math.PI ?
"end" : "start";
})
.text(function(d, i) { return d.value.toFixed(2); });

d3.js - how to automatically calculate arc lengths in radial dendrogram

I'm creating a modified version of Mike Bostock's hierarchical edge bundling diagram:
http://mbostock.github.com/d3/talk/20111116/bundle.html
but I want to make arcs which span certain groups of data, like this:
I'm currently just hardcoding the length of the arc, but I want to do it dynamically. How can I accomplish this? Here's my current code:
/* MH - USER DEFINED VARIABLES */
var chartConfig = { "Tension" : .85, "canvasSize" : 800, "dataFile" : "../data/projects.json", "linePadding" : 160, "textPadding" : 30, "arcPadding" : 5, "arcWidth" : 30 }
var pi = Math.PI;
var radius = chartConfig.canvasSize / 2,
splines = [];
var cluster = d3.layout.cluster() //Cluster is the diagram style, a node to link dendrogram dendrogram (tree diagram)
.size([360, radius - chartConfig.linePadding]); //MH - sets the size of the circle in relation to the size of the canvas
var bundle = d3.layout.bundle(); //Bundles the node link lines so that they spread at the end but keep close initially
var arcInner = radius - chartConfig.linePadding + chartConfig.arcPadding;
var arcOuter = arcInner + chartConfig.arcWidth;
var arc = d3.svg.arc().innerRadius(arcInner).outerRadius(arcOuter);
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(chartConfig.Tension) //How tightly to bundle the lines. No tension creates straight lines
.radius(function(d) { return d.y; })
.angle(function(d) { return d.x / 180 * Math.PI; });
var vis = d3.select("#chart").append("svg")
.attr("width", radius * 2)
.attr("height", radius * 2)
.attr("class","svg")
.append("g")
.attr("class","chart")
.attr("transform", "translate(" + radius + "," + radius + ")");
d3.json(chartConfig.dataFile, function(classes) {
var nodes = cluster.nodes(packages.root(classes)),
links = packages.imports(nodes),
splines = bundle(links);
var path = vis.selectAll ("path.link")
.data(links)
.enter().append("path")
.attr("class", function(d){ return "link source-" + d.source.key + " target-" + d.target.key; })
.attr("d", function(d,i){ return line(splines[i]); });
vis.selectAll("g.node")
.data(nodes.filter(function(n) { return !n.children; }))
.enter().append("g")
.attr("class", "node")
.attr("id",function(d){ return "node-" + d.key; })
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.append("text")
.attr("dx", function(d) { return d.x < 180 ? chartConfig.textPadding : -chartConfig.textPadding; }) //dx Moves The text out away from the lines in a positive or negative direction, depending on which side of the axis it is on
.attr("dy", ".31em") //moves the text up or down radially around the circle
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.text(function(d) {
textString = d.key;
textString = textString.split('_').join(' '); //MH replace underscores with spaces
return textString;
})
.on("mouseover",textOver)
.on("mouseout",textOut);
});
/* ARCS ARE HARDCODED, SHOULD BE DYNAMIC */
var arcData = [
{aS: 0, aE: 45,rI:radius - chartConfig.linePadding + chartConfig.arcPadding,rO:radius - chartConfig.linePadding + chartConfig.textPadding-chartConfig.arcPadding}
];
var arcJobsData = d3.svg.arc().innerRadius(arcData[0].rI).outerRadius(arcData[0].rO).startAngle(degToRad(1)).endAngle(degToRad(15));
var g = d3.select(".chart").append("svg:g").attr("class","arcs");
var arcJobs = d3.select(".arcs").append("svg:path").attr("d",arcJobsData).attr("id","arcJobs").attr("class","arc");
g.append("svg:text").attr("x",3).attr("dy",15).append("svg:textPath").attr("xlink:href","#arcJobs").text("JOBS").attr("class","arcText"); //x shifts x pixels from the starting point of the arc. dy shifts the text y units from the top of the arc
...
function degToRad(degrees){
return degrees * (pi/180);
}
function updateNodes(name,value){
return function(d){
if (value) this.parentNode.appendChild(this);
vis.select("#node-"+d[name].key).classed(name,value);
}
}
I've seen your json data structure here: http://mikeheavers.com/transfers/projects/data/projects.json. Firstly, in order to group the data and append the tag correctly, it'll be better to change your data like this: https://raw.github.com/gist/4172625/4de3e6a68f9721d10e0068d33d1ebb9780db4ae2/flare-imports.json to create a hirarchical structure.
We can then use the groups to draw the arcs.
First we create groups by "selectAll" and filter your nodes. Here you could add other group names of your data:
var groupData = svg.selectAll("g.group")
.data(nodes.filter(function(d) {return (d.key=='Jobs' || d.key == 'Freelance' || d.key == 'Bayard') && d.children; }))
.enter().append("group")
.attr("class", "group");
I just checked that in my case, so you'd better verify the result of the filter and make change according to your case (our data structure is a little bit different).
Now we got a list of groups. Then we'll go through the children of each group, and choose the smallest and largest x as the start and end angle. We can create a function like this:
function findStartAngle(children) {
var min = children[0].x;
children.forEach(function(d){
if (d.x < min)
min = d.x;
});
return degToRad(min);
}
And similarly a findEndAngle function by replacing min by max. Then we can create the arcs' format:
var groupArc = d3.svg.arc()
.innerRadius(arcData[0].rI)
.outerRadius(arcData[0].rO)
.startAngle(function(d){return findStartAngle(d.children);})
.endAngle(function(d){return findEndAngle(d.children);});
Then we can create arcs in "dynamic" way:
svg.selectAll("g.arc")
.data(groupData[0])
.enter().append("arc")
.attr("d", groupArc)
.attr("class", "arc")
.append("svg:text")
...;
In my case it is groupData[0], maybe you should check it in your case.
For adding tags to arcs you just need to add d.key or d.name according to the result of your selection.
The full code is available here: https://gist.github.com/4172625. Every time I get json from database so if there's no dynamic way to generic arcs I will be dead :P Hope it helps you!

Categories