AngularJS D3JS Donut chart colour change of arc on hover - javascript

I am creating donut using d3.js with AngularJS. Here is my Donut, by default I need to have same color for all the arcs of Donut chart, which is working fine. Now on hover of the particular arc I need to change the color of that particular arc to blue, which is not working. Can any one help me in this?
Below is the answer from the refernce plunker provided. :
scope.mouseOverPath = function(d) {
d3.select(this)
.transition()
.duration(duration)
.style("fill", "blue")
.each("start", function(d) {
labelRadius = radius + chartConfig.labelPaddingOver;
d3.select(this.parentNode).select('.legend')
.transition()
.attr("transform", function(d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
height = Math.sqrt(x * x + y * y);
return "translate(" + (x / height * labelRadius) + ',' +
(y / height * labelRadius) + ")";
});
})
...
}
scope.mouseOutPath = function(d) {
d3.select(this)
.transition()
.style('fill', function(d) {
return color(d.data.label);
})
.each("start", function(d) {
labelRadius = radius + chartConfig.labelPadding;
d3.select(this.parentNode).select('.legend')
.transition()
.attr("transform", function(d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
height = Math.sqrt(x * x + y * y);
return "translate(" + (x / height * labelRadius) + ',' +
(y / height * labelRadius) + ")";
});
})
...
}

Using d3's style method inside your directive's scope.mouseOverPath and scope.mouseOutPath methods did the trick.
https://plnkr.co/edit/P98rPVKOHOgN5fKB

Related

d3 donut chart transform: translateY

I am making a graph in which it shows the percentage of each data type like this:
but I have a problem copy the code and I have created it the same as the previous example but the titles put them above the percentage and I would like to fix it by placing them above the numbers as it is in the first image, since I have it like this:
This is the code where I send to call the text and in example it already throws me a transformation but I want to upload it so that it remains as a title
svg
.selectAll("allLabels")
.data(name_ready)
.enter()
.append("text")
.text(function (d) {
console.log(d.data.key);
return d.data.key;
})
.style("font-size", "1rem")
.attr("transform", function (d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1);
return "translate(" + pos + ")";
})
.attr("class", "fontDonut")
.style("text-anchor", function (d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
return midangle < Math.PI ? "start" : "end";
});
The code is like this since it is automatic and it positions itself depending on how much data there is
// set the dimensions and margins of the graph
var width = 400;
var height = 250;
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = 100;
// append the svg object to the div called 'my_dataviz'
var svg = d3
.select("#my_char")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Create dummy data
var data = {
"25%": 25,
"30% ": 30,
"20%": 20,
"25% ": 25
}
var name = {
"Married": 30,
"Divorced": 30,
"Single": 40,
"Single2 ": 25
}
// set the color scale
var color = d3
.scaleOrdinal([`#C8DBFB`, `#93B6F8`, `#256EF1`]);
// Compute the position of each group on the pie:
var pie = d3
.pie()
.sort(null) // Do not sort group by size
.value(function(d) {
return d.value;
});
var data_ready = pie(d3.entries(data));
var name_ready = pie(d3.entries(name));
// The arc generator
var arc = d3
.arc()
.innerRadius(radius * 0.6) // This is the size of the donut hole
.outerRadius(radius * 0.8);
// Another arc that won't be drawn. Just for labels positioning
var outerArc = d3
.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
svg
.selectAll("allSlices")
.data(data_ready)
.enter()
.append("path")
.attr("d", arc)
.attr("fill", function(d) {
return color(d.data.key);
})
.style("opacity", 0.7);
// Add the polylines between chart and labels:
svg
.selectAll("allLabels")
.data(data_ready)
.enter()
.append("text")
.text(function(d) {
console.log(d.data.key);
return d.data.key;
})
.style("font-size", "2rem")
.style("font-weight", "700")
.attr("transform", function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1);
return "translate(" + pos + ")";
})
.style("text-anchor", function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
return midangle < Math.PI ? "start" : "end";
});
svg
.selectAll("allLabels")
.data(name_ready)
.enter()
.append("text")
.text(function(d) {
console.log(d.data.key);
return d.data.key;
})
.style("font-size", "1rem")
.attr("transform", function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1);
return "translate(" + pos + ")";
})
.attr("class", "fontDonut")
.style("text-anchor", function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
return midangle < Math.PI ? "start" : "end";
});
.fontDonut {
margin-top: 8rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
<div class="text-center bg-white shadow py-6 px-6 chart-container">
<div class="flex justify-start font-bold mb-4 ">
<p class="font-bold ml-4">Children</p>
</div>
<div id="my_char" />
</div>
Don't treat the two values as separate, but use them like a bloc instead. I used basic trigonometry to find the angle of the midpoint like you did, but then draw a line from the centre, so the label is aligned with the middle of the arc and all labels are the same distance from the donut.
Then, I don't have to fiddle with the labels, and instead just add the percentage on top, with a small offset. Note that margin only works in HTML, not in SVG.
// set the dimensions and margins of the graph
var width = 400;
var height = 250;
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = 100;
// append the svg object to the div called 'my_dataviz'
var svg = d3
.select("#my_char")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Create dummy data
var data = {
//"Married": 30,
"Divorced": 30,
"Single": 40,
"Single2 ": 25
}
// set the color scale
var color = d3
.scaleOrdinal([`#C8DBFB`, `#93B6F8`, `#256EF1`, `darkblue`]);
// Compute the position of each group on the pie:
var pie = d3
.pie()
.sort(null) // Do not sort group by size
.value(function(d) {
return d.value;
});
var data_ready = pie(d3.entries(data));
// The arc generator
var arc = d3
.arc()
.innerRadius(radius * 0.6) // This is the size of the donut hole
.outerRadius(radius * 0.8);
// Another arc that won't be drawn. Just for labels positioning
var outerArc = d3
.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
svg
.selectAll("path")
.data(data_ready)
.enter()
.append("path")
.attr("d", arc)
.attr("fill", function(d) {
return color(d.data.key);
})
.style("opacity", 0.7);
// Add the polylines between chart and labels:
const labelGroup = svg
.selectAll(".labelGroup")
.data(data_ready)
.enter()
.append("g")
.attr("class", "labelGroup")
// Transform the whole group, not the individual text items
.attr("transform", function(d) {
// Get the angle
var midAngle = d.startAngle + (d.endAngle - d.startAngle) / 2;
// Define the radius
var textRadius = 1.4 * radius;
// Use trigonometry to find the correct position
var x = Math.sin(midAngle) * textRadius;
var y = -Math.cos(midAngle) * textRadius;
y = Math.min(y, height / 2 - 20);
return "translate(" + [x, y] + ")";
})
.style("text-anchor", "middle");
labelGroup
.append("text")
.text(function(d) {
return d.data.key;
})
.attr("dominant-baseline", "hanging")
.attr("dy", 5)
.style("font-size", "2rem")
.style("font-weight", "700");
labelGroup
.append("text")
.text(function(d) {
return d.data.value + "%";
})
.attr("dy", -5)
.style("font-size", "1rem");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
<div class="text-center bg-white shadow py-6 px-6 chart-container">
<div class="flex justify-start font-bold mb-4 ">
<p class="font-bold ml-4">Children</p>
</div>
<div id="my_char" />
</div>

Iterative/chained transitions along line graph with discrete points and delay

I created a jsfiddle here.
I do have a graph - in this case a sine wave - and want to move a circle along this line (triggered by a click event), stop at certain x and y value pairs that are on this graph and then move on to the last point of the graph from where it jumps to the first again (ideally this should go on until I press a stop button).
My current problem is that the circle only moves horizontally but not in the ordinate direction and also the delay is visible only once (in the very beginning).
The relevant code is this one (the entire running example can be found in the link above):
Creation of the circle:
// the circle I want to move along the graph
var circle = svg.append("circle")
.attr("id", "concindi")
.attr("cx", x_scale(xval[0]))
.attr("cy", y_scale(yval[0]))
.attr("transform", "translate(" + (0) + "," + (-1 * padding + 15) + ")")
.attr("r", 6)
.style("fill", 'red');
The moving process:
var coordinates = d3.zip(xval, yval);
svg.select("#concindi").on("click", function() {
coordinates.forEach(function(ci, indi){
//console.log(ci[1] + ": " + indi);
//console.log(coordinates[indi+1][1] + ": " + indi);
if (indi < (coordinates.length - 1)){
//console.log(coordinates[indi+1][1] + ": " + indi);
console.log(coordinates[indi + 1][0]);
console.log(coordinates[indi + 1][1]);
d3.select("#concindi")
.transition()
.delay(2000)
.duration(5000)
.ease("linear")
.attr("cx", x_scale(coordinates[indi + 1][0]))
.attr("cy", y_scale(coordinates[indi + 1][1]));
}
});
I am pretty sure that I use the loop in a wrong manner. The idea is to start at the first x/y pair, then move to the next one (which takes 5s), wait there for 2s and move on to the next and so on. Currently, the delay is only visible initially and then it just moves horizontally.
How would this be done correctly?
Why don't you use Bostock's translateAlong function?
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
Here is the demo:
// function to generate some data
function get_sin_val(value) {
return 30 * Math.sin(value * 0.25) + 35;
}
var width = 400;
var height = 200;
var padding = 50;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var xrange_min = 0;
var xrange_max = 50;
var yrange_min = 0;
var yrange_max = 100;
var x_scale = d3.scale.linear()
.domain([xrange_min, xrange_max])
.range([padding, width - padding * 2]);
var y_scale = d3.scale.linear()
.domain([yrange_min, yrange_max])
.range([height - padding, padding]);
// create the data
var xval = d3.range(xrange_min, xrange_max, 1);
var yval = xval.map(get_sin_val);
// just for convenience
var coordinates = d3.zip(xval, yval);
//defining line graph
var lines = d3.svg.line()
.x(function(d) {
return x_scale(d[0]);
})
.y(function(d) {
return y_scale(d[1]);
})
.interpolate("linear");
//draw graph
var sin_graph = svg.append("path")
.attr("d", lines(coordinates))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
// the circle I want to move along the graph
var circle = svg.append("circle")
.attr("id", "concindi")
.attr("transform", "translate(" + (x_scale(xval[0])) + "," + (y_scale(yval[0])) + ")")
.attr("r", 6)
.style("fill", 'red');
svg.select("#concindi").on("click", function() {
d3.select(this).transition()
.duration(5000)
.attrTween("transform", translateAlong(sin_graph.node()));
});
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
You have to understand that forEach will loop to the end of the array almost instantaneously. Thus, you cannot make the circle jumping to one coordinate to the other with your approach right now (thus, unfortunately, you are correct here:"I am pretty sure that I use the loop in a wrong manner").
If you want to add the 2s waiting period between one point and another, the best idea is chaining the transitions. Something like this (I'm reducing the delay and the duration times in the demo, so we can better see the effect):
var counter = 0;
transit();
function transit() {
counter++;
d3.select(that).transition()
.delay(500)
.duration(500)
.attr("transform", "translate(" + (x_scale(coordinates[counter][0]))
+ "," + (y_scale(coordinates[counter][1])) + ")")
.each("end", transit);
}
Here is the demo:
// function to generate some data
function get_sin_val(value) {
return 30 * Math.sin(value * 0.25) + 35;
}
var width = 400;
var height = 200;
var padding = 50;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var xrange_min = 0;
var xrange_max = 50;
var yrange_min = 0;
var yrange_max = 100;
var x_scale = d3.scale.linear()
.domain([xrange_min, xrange_max])
.range([padding, width - padding * 2]);
var y_scale = d3.scale.linear()
.domain([yrange_min, yrange_max])
.range([height - padding, padding]);
// create the data
var xval = d3.range(xrange_min, xrange_max, 1);
var yval = xval.map(get_sin_val);
// just for convenience
var coordinates = d3.zip(xval, yval);
//defining line graph
var lines = d3.svg.line()
.x(function(d) {
return x_scale(d[0]);
})
.y(function(d) {
return y_scale(d[1]);
})
.interpolate("linear");
//draw graph
var sin_graph = svg.append("path")
.attr("d", lines(coordinates))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none");
// the circle I want to move along the graph
var circle = svg.append("circle")
.attr("id", "concindi")
.attr("transform", "translate(" + (x_scale(xval[0])) + "," + (y_scale(yval[0])) + ")")
.attr("r", 6)
.style("fill", 'red');
svg.select("#concindi").on("click", function() {
var counter = 0;
var that = this;
transit();
function transit() {
counter++;
d3.select(that).transition()
.delay(500)
.duration(500)
.attr("transform", "translate(" + (x_scale(coordinates[counter][0])) + "," + (y_scale(coordinates[counter][1])) + ")")
.each("end", transit);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Specify startpoint to interpolate a circle on an arc when clicked on by the user

How do I provide the starting point of an arc for moving a circle along the path of the arc. I have a world map with several arcs displayed on it. I wish to interpolate the movement of a circle on the arc that has been selected by the user using the .on('click') event. I wish to know how I can identify the startPoint of the arc in question.
Specifically, I am not able to understand what parameters to provide in .attr("transform", "translate(" + startPoint + ")") attribute of the circle to enable the circle to start from the starting position of the arc.
At present, it passes the entire path and I receive the following error
d3.v3.min.js:1 Error: attribute transform: Expected number, "translate(M1051.5549785289…".
Although, surprisingly, the circle marker appears on the screen and interpolates along the first arc that has been drawn. However, I wish to change this interpolation to an arc that has been clicked by the user. In other words, how do I feed a new startPoint to the circle marker every time the user clicks on a different arc and to have a subsequent interpolation of the same.
var path3 = arcGroup.selectAll(".arc"),
startPoint = pathStartPoint(path3)
var marker = arcGroup.append("circle")
marker.attr("r", 7)
.attr("transform", "translate(" + startPoint + ")")
transition();
function pathStartPoint(path) {
var d = path.attr("d")
console.log(path)
dsplitted = d.split(" ");
return dsplitted[0].split(",");
}
function transition() {
marker.transition()
.duration(7500)
.attrTween("transform", translateAlong(path3.node()))
.each("end", transition);// infinite loop
}
function translateAlong(path) {
var l = path.getTotalLength();
return function(i) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";//Move marker
}
}
}
As #altocumulus states in the their comment, getPointAtLength doesn't need a starting point. It takes as an argument a distance from 0 to path length where 0 is the starting point. Here's a quick example, click on any path below:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var w = 400,
h = 400;
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h);
var data = []
for (var i = 0; i < 5; i++) {
data.push({
x0: Math.random() * w,
y0: Math.random() * h,
x1: Math.random() * w,
y1: Math.random() * h
});
}
var marker = svg.append("circle")
.attr("r", 20)
.style("fill", "steelblue")
.style("opacity", 0);
svg.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", function(d) {
var dx = d.x1 - d.x0,
dy = d.y1 - d.y0,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.x0 + "," + d.y0 + "A" + dr + "," + dr +
" 0 0,1 " + d.x1 + "," + d.y1;
})
.style("stroke", function(d, i) {
return d3.schemeCategory10[i];
})
.style("stroke-width", "10px")
.style("fill", "none")
.on("click", function(d){
marker
.style("opacity", 1)
.transition()
.duration(1000)
.attrTween("transform", translateAlong(this))
.on("end", function(d) {
marker.style("opacity", 0);
});
});
function translateAlong(path) {
var l = path.getTotalLength();
return function(i) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")"; //Move marker
}
}
}
</script>
</body>
</html>

Issue labeling d3 sunburst

I am developing a d3 sunburst type.
Everything is ok, It is taking the flare JSON correctly but, when I go to label the path look what is happening:
The code is the following:
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
var hue = d3.scale.ordinal().range(["#feec76","#aec7e8","#ff00bf","#7f7f7f"]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
d3.json("http://api.printoriente.com/treemap.php", function(error, root) {
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.style("fill", function(d) { return hue((d.children ? d : d.parent).name); })
.on("click", click);
var text = g.append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) { return y(d.y); })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.name; });
And the rotation code is:
function computeTextRotation(d) {
return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
}
This script works for all others d3 but I have to put those colors for each path.
Where is the problem?
Regards.
UPDATED: d3 sunburst with small font-size:
UPDATED: I want something like this:
UPDATED: Take a look of internal labels:
I'm not sure where you got the code to calculate the angle from, but it seems to be completely off. If you look at this example, the code to compute the angle is (with everything else being equal)
var angle = (d.x + d.dx / 2) * 180 / Math.PI - 90;
Replacing the code in your example with that fixes the angles. To fix the positions, you can adjust the dx offset of the labels, e.g.
.attr("dx", 50")
Complete example here.

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); });

Categories