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) + ')';
});
Related
I am using D3.js for half pie chart in Angular 12 app. I added needle, but I want to have text at end of needle to show the value as in the image
Needle with Value
I got needle, but unable to render the value at the end of needle.
My code
this.svg = d3
.select('div#pie')
.append('svg')
.attr('width',this.svgWidth)
.attr('height',this.svgHeight)
.attr('id','pie-svg')
.append('g')
.attr('class','ps')
.attr(
'transform',
'translate('+this.svgWidth/2+',115)');
this.colorRanges = d3.scaleOrdinal()
.domain(['a', 'b', 'c'])
.range(this.colors)
this.pie = d3
.pie()
.startAngle(-90 * (Math.PI / 180))
.endAngle(90 * (Math.PI / 180))
.value((d: any,i:number) => d[1])
.sort((a:any, b:any) => d3.ascending(a[0], b[0]));
this.svg.selectAll('rect')
.data(this.pie(Object.entries(this.pieData)))
.join('path')
.attr('d',
d3.arc().innerRadius(75).outerRadius(35))
.attr('fill', (d:any,i:number) => this.colorRanges(d3.schemeSet2[i]))
.attr('stroke','none')
.style('stroke-width','1px')
.style('opacity',1);
this.svg
.selectAll('.needle')
.data([0])
.enter()
.append('line')
.attr('x1', -10)
.attr('x2', -75)
.attr('y1', 0)
.attr('y2', 0)
.classed('needle', true)
.attr('transform', 'rotate(135)');
I tried adding few codes as below from internet and failed.
var labelArc = this.svg.arc()
.outerRadius(75)
.innerRadius(35);
this.svg.append("text")
.attr("transform", function(d: any) {
return "translate(" + labelArc.centroid(d) + ")"; })
.attr("dy", ".35em")
.text("75");
As I am new to D3 and Angular, unable to proceed.
Could someone please help to get the text on top of needle.
You may try with appending a new "g" which inserts new graphic, the position could be near the needle, but would need some workarounds.
Using this example, I have been able to create D3's scatter plot with text labels with the scatter points.
I would like to rotate each label to a certain degree, however, when I attempt to do this, all of the text as a whole is rotated on one axis, not individual axis`.
Here is my code:
svg.selectAll("circle")
.data(data)
.enter()
.append('circle')
.attr('fill', '#4E5FF3')
.attr('stroke', 'none')
.attr('cx', d => { return x(Date.parse(d.date)) })
.attr('cy', d => { return y(d.totalValue) })
.attr('r', 3);
svg.selectAll("text")
.data(data)
.enter()
.append('text')
.attr('x', d => { return x(Date.parse(d.date)) })
.attr('y', d => { return y(d.totalValue) })
.text(d => {
return 'Total: ' + d.totalValue + ' - Month: ' + d.monthValue;
})
.attr('transform','rotate(5)translate(0, 0)');
How do I transform each label on an individual axis, rather than all of the labels on one axis?
Instead of using a single value for rotate...
.attr('transform','rotate(5)translate(0, 0)');
...which will rotate the text around the origin (0,0), use the texts' positions in the rotate function, as the optional x and y parameters:
rotate(<a> [<x> <y>])
In the bl.ocks you linked, that would be (using commas):
.attr('transform',function(d){
return "rotate(5," + xScale(d[0]) + "," + yScale(d[1]) + ")"
});
Here is the updated bl.ocks: https://bl.ocks.org/GerardoFurtado/45fa2b852f8b0f229923c6dc1cdfa2b6/cf0917330d3d2775efd83a83c733c544d0338ea2
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.
UPDATE: New JSFIDDLE Scaling now working, ditched the defs and rect altogether and just appended the image. But still stuck on translate.
The translating is still not working on zoom. I can set the translate to say -100 for both x and y to get the non-zoomed placement correct. But, when zooming, it's of course still translating it -100 and not the larger value it would need to be to keep it in place.
Appears to need something in the code in the zoom section toward the bottom. Been messing with the part currently commented out, but no luck so far.
// .attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; })
// .attr("x", function(d) { return d.r * k; })
// .attr("y", function(d) { return d.r * k; })
.attr("width", function(d) { return d.r * k; })
.attr("height", function(d) { return d.r * k; })
Here's JSFIDDLE. I have a d3 circle packing with a raster image inside an svg rect within each node. How do you make the image scale when zooming? The container scales, but the image stays small and repeats when zoomed. Been trying to set the defs correctly, but no luck.
var defs = svg.append("defs")
// .data(nodes)
// .enter()
.append("pattern")
.attr("id", "bg")
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', imageWidthHeight)
.attr('height', imageWidthHeight)
// .attr("transform", "translate(40,80)")
.append("image")
// .html("xlink:href", "img/" + function(d) { return d.image; })
.attr("xlink:href", "http://www.public-domain-photos.com/free-stock-photos-4/travel/yosemite/yosemite-meadows.jpg")
.attr('width', imageWidthHeight)
.attr('height', imageWidthHeight)
// .attr("transform", "translate(40,80)");
Also, can't get the container/image to translate into the center of the circle. I've commented those bits out for now because it screws everything up.
Have tried to apply info from these discussions, but still stuck. Thanks.
http://bl.ocks.org/mbostock/950642#graph.json
https://groups.google.com/forum/#!topic/d3-js/fL8_1BLrCyo
How to fill D3 SVG with image instead of colour with fill?
Adding elements to a D3 circle pack nodes
Answer JSFIDDLE
Got it. The trick was changing this bit of horrible:
(d.x - v[0]) * k
to this even worse bit of horrible:
(((d.x - v[0]) * (k)) - ((d.r / 2) * k))
Then the same for y.
Don't get me wrong, I'm grateful for the zoom circle pack template and the genius(es) who put it together. Thank you. It's just for someone at my noob level, the code above looks like a punishment of some kind. :)
I'm using d3.js to create a donut chart with labels on the outside. Using some trigonometry based on the centroids of each slice of the pie, I position the labels.
g.append("g")
.attr("class", "percentage")
.append("text")
.attr("transform", function(d)
{
var c = arc.centroid(d);
var x = c[0];
var y = c[1];
var h = Math.sqrt(x*x + y*y);
return "translate(" + (x/h * obj.labelRadius) + ',' + (y/h * obj.labelRadius) + ")";
}
)
.attr("dy", ".4em")
.attr("text-anchor", function(d)
{
return (d.endAngle + d.startAngle)/2 > Math.PI ? "end" : "start";
}
)
.text(function(d) { return d.data.percentage+"%"; });
What I'm ultimately trying to accomplish is to rearrange labels that are outside the edges of the pie chart, to prevent overlaps.
One of the ways I have tried to solve the problem is to define set "anchor points", where labels can be positioned, guaranteeing that they will no overlap. Problem is mapping the centroids to the anchors and preserving some sense of visual correspondence between the slices and the labels (Specially difficult when slices are slim).
Image above shows the possible location of the anchors (centroids of the slices shown). With these positions it is impossible to have an overlap.
Adding complexity to the problem is the fact that when labels (they're horizontal) are close to the top or bottom of the pie, they are more easily overlapped, than when they are on the right or left of the pie.
Any ideas on how to approach this problem?
[EDIT] Following the suggestion of meetamit, I implemented the following:
.attr("dy", function(d)
{
var c = arc.centroid(d);
var x = c[0];
var y = c[1];
var h = Math.sqrt(x*x + y*y);
var dy = y/h * obj.labelRadius;
dy=dy*fontSizeParam*.14/heightParam);
return (dy)+"em";
})
It helps a bit, and gives some room to the labels, still looking for a solution that will cover all cases though...
Can't you create two arcs? one for the chart, and one for the labels?
// first arc used for drawing the pie chart
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
// label attached to first arc
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
// second arc for labels
var arc2 = d3.svg.arc()
.outerRadius(radius + 20)
.innerRadius(radius + 20);
// label attached to second arc
g.append("text")
.attr("transform", function(d) { return "translate(" + arc2.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
There is a good, pragmatic d3.js-based solution by programmer John Williams presented here:
https://www.safaribooksonline.com/blog/2014/03/11/solving-d3-label-placement-constraint-relaxing/
It should work well for cases with reasonable restrictions, e.g. a maximum of 12 labels as discussed above. There are also pointers in the article to more advanced algorithms, but this simple approach may actually, when used with sufficient label-content constraints, give results that have a more orderly visual appearance than other methods would yield.