Switch d3 donut arc on selection - javascript

I have more than one donut in my page, each donut will have a thinner portion (like unfilled) and another arc colored.
When user clicks on the colored arc, it should have a white border. And if user selects other arc (unfilled) the unfilled area get filled with color, changes the width like the other one and will have border, same time the filled one gets unfilled. To summarize the arc colored portion should get switched on selection.
Can I achieve this by applying class/styles? In one page there should be only one arc selected at a time, all other selections will be cleared.
// data
var dataset = [{
color: "#5FC5EA",
value: 70
}, {
color: "transparent",
value: 30
}];
// size
var width = 460,
z
height = 300,
radius = Math.min(width, height) / 2;
var pie = d3.layout.pie()
.sort(null).value(function(d) {
return d.value;
});
// thin arc
var arc1 = d3.svg.arc()
.innerRadius(radius - 20)
.outerRadius(radius - 11);
// main arc
var arc = d3.svg.arc()
.innerRadius(radius - 30)
.outerRadius(radius);
// set svg
var svg = d3.select("#d3-setup-donut").append("svg")
.attr("class", 'donut-chart')
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.on('mouseout', function() {
d3.selectAll('.donut-tooltip').style('display', 'none');
});
// tooltip
var div = d3.select("body")
.append("div")
.attr("class", "donut-tooltip");
// draw thinner arc
var path = svg.selectAll(".background")
.data(pie([{
color: "#222427",
value: 1
}]))
.enter().append("path")
.attr("class", "background")
.attr("fill", function(d, i) {
return d.data.color;
})
.attr("d", arc1)
.on('click', function(d, i) {
//
})
.on("mousemove", function(d, i) {
var mouseVal = d3.mouse(this);
div.style("display", "none");
div.html(d.data.label + " : " + d.value)
.style("left", (d3.event.pageX - 40) + "px")
.style("top", (d3.event.pageY - 35) + "px")
.style("opacity", 1)
.style("display", "block");
})
.on("mouseout", function() {
div.html(" ").style("display", "none");
});
// draw main arc
var path = svg.selectAll(".foreground")
.data(pie(dataset))
.enter().append("path")
.attr("class", "foreground")
.attr("fill", function(d, i) {
return d.data.color;
})
.attr("d", arc)
.on('click', function(d, i) {
d3.select(this)
.classed('selected', true);
})
.on("mousemove", function(d, i) {
var mouseVal = d3.mouse(this);
div.style("display", "none");
div.html(d.data.label + " : " + d.value)
.style("left", (d3.event.pageX - 40) + "px")
.style("top", (d3.event.pageY - 55) + "px")
.style("opacity", 1)
.style("display", "block");
})
.on("mouseout", function() {
div.html(" ").style("display", "none");
});
// draw inner text
svg.append("text")
.text('60%')
.attr("class", "donut-inner-val")
//.attr("x", radius/12 - 30)
//.attr("y", radius/12 - 10);
svg.append("text")
.text('in progress')
.attr("class", "donut-inner-text")
.attr("x", (radius / 12) - 35)
.attr("y", (radius / 12) + 10);
JSFiddle

Try this Code
.on('click', function(d, i) {
d3.selectAll(".foreground").classed('selected', false);
if(d3.select(this).classed("active")){
d3.select(this)
.classed('selected', true);
}else{
d3.selectAll(".foreground").classed("active", false);
d3.select(this).classed("active",true);
d3.select(this)
.classed('selected', true);
}
})
DEMO

Related

SVG line out of arc starting position - d3.js

I'm trying to achieve this
but this is currently what I have
Essentially all I need to do is figure out how to have the lines emanating out from the circle starting at the arc start.
My question is exactly that, how can I translate the arc starting position into the x1,y1 attribute of an svg line. Below is the code that I currently have pertaining to drawing the lines:
// Draw lines emanating out
g.append('line')
.attr('class', 'outer-line')
.attr('x1', function(d) {
return 0;
})
.attr('x2', 0)
.attr('y1', -radius)
.attr('y2', -radius-150)
.attr('stroke', function(d, i) {
return color(i);
})
.attr('stroke-width','2')
.attr("transform", function(d) {
return "rotate(" + (d.startAngle+d.endAngle)/2 * (180/Math.PI) + ")";
});
If I understand your problem correctly, you can use just d.startAngle:
g.attr("transform", function(d) {
return "rotate(" + d.startAngle * (180/Math.PI) + ")";
});
Check here an example (click on "run code snippet"):
var dataset = [300, 200, 400, 200, 300, 100, 50];
var width = 460,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 50);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(pie(dataset))
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc);
var g = svg.selectAll(".groups")
.data(pie(dataset))
.enter()
.append("g");
g.append('line')
.attr('class', 'outer-line')
.attr('x1', 0)
.attr('x2', 0)
.attr('y1', -radius + 50)
.attr('y2', -radius)
.attr('stroke', 'black')
.attr('stroke-width','2')
.attr("transform", function(d) {
return "rotate(" + d.startAngle * (180/Math.PI) + ")";
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Adding labels outside d3 pie chart not working

I am trying to add labels in a d3 pie as displayed at http://bl.ocks.org/dbuezas/9306799 but it is always displays the labels inside the slices.
var dataset = ${pieList};
var width = 700,
height = 700,
outerRadius = Math.min(width, height) / 2,
innerRadius = outerRadius * .999,
innerRadiusFinal = outerRadius * .5,
innerRadiusFinal3 = outerRadius * .45,
color = d3.scale.category20()
;
var vis = d3.select("#pieChart")
.append("svg:svg")
.data([dataset])
.attr("width", width)
.attr("height", height)
.append("svg:g")
.attr("transform", "translate(" + outerRadius + "," + outerRadius + ")")
;
var arc = d3.svg.arc()
.outerRadius(outerRadius).innerRadius(innerRadius);
var arcFinal = d3.svg.arc().innerRadius(innerRadiusFinal).outerRadius(outerRadius);
var arcFinal3 = d3.svg.arc().innerRadius(innerRadiusFinal3).outerRadius(outerRadius);
var pie = d3.layout.pie()
.value(function (d) {
return d.measure;
});
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
.append("svg:g")
.attr("class", "slice")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", up)
;
arcs.append("svg:path")
.attr("fill", function (d, i) {
return color(i);
})
.attr("d", arc)
.append("svg:title")
.text(function (d) {
return d.data.category + ": " + formatAsPercentage(d.data.measure);
});
d3.selectAll("g.slice").selectAll("path").transition()
.duration(750)
.delay(10)
.attr("d", arcFinal)
;
arcs.filter(function (d) {
return d.endAngle - d.startAngle > .2;
})
.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("transform", function (d) {
return "translate(" + arcFinal.centroid(d) + ")rotate(" + angle(d) + ")";
})
.text(function (d) {
return d.data.category;
})
;
.attr("transform", function (d) {
return "translate(" + arcFinal.centroid(d)
+ ")rotate(" + angle(d)
+ ")translate(" + 700 + ",0)";
})
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
// Pie chart title
vis.append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text("Attendance report 2015")
.attr("class", "title")
;
function mouseover() {
d3.select(this).select("path").transition()
.duration(750)
//.attr("stroke","red")
//.attr("stroke-width", 1.5)
.attr("d", arcFinal3)
;
}
function mouseout() {
d3.select(this).select("path").transition()
.duration(750)
//.attr("stroke","blue")
//.attr("stroke-width", 1.5)
.attr("d", arcFinal)
;
}
function up(d, i) {
/* update bar chart when user selects piece of the pie chart */
//updateBarChart(dataset[i].category);
updateBarChart(d.data.category, color(i));
updateLineChart(d.data.category, color(i));
}
I want to display the labels outside of the pie slices but it always display inside the slices.
One option would be to translate your text outward by the radius of the pie, which might be easiest to do after the rotate. So, something like:
.attr("transform", function (d) {
return "translate(" + arcFinal.centroid(d)
+ ")rotate(" + angle(d)
+ ")translate(" + radius + ",0)";
})
Where radius is the radius of your pie chart. Sometimes you might want to translate a few pixels further, so that you have a margin between the chart and the label.
This is certainly not the only answer, here are some more issues to watch out for though:
Rotating your text can end up with it being upside down on one side of the graph, which is not very readable.
After fixing the rotation issue, you might find that your label's textanchor causes long labels to go over the graph anyway.
If your pie slices are thin, you can end up writing labels over one another.

Change single chord color in chord diagram using D3.js

D3 and Javascript newbie here. I'd like to change the color of a single chord in a chord diagram that is rendered with D3. Ideally, this color can be arbitrary, with no relationship to the source/destination entities of the chord.
How do I identify a single chord so I can later access it to fill it?
Here's an image (poorly edited with an image editor) with the desired effect (green chord).
var matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
var chord = d3.layout.chord()
.padding(.05)
.sortSubgroups(d3.descending)
.matrix(matrix);
var width = 960,
height = 500,
innerRadius = Math.min(width, height) * .41,
outerRadius = innerRadius * 1.1;
var fill = d3.scale.ordinal()
.domain(d3.range(4))
.range(["#000000", "#FFDD89", "#957244", "#F26223"]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.append("g").selectAll("path")
.data(chord.groups)
.enter().append("path")
.style("fill", function(d) { return fill(d.index); })
.style("stroke", function(d) { return fill(d.index); })
.attr("d", d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius))
.on("mouseover", fade(.1))
.on("mouseout", fade(1));
var ticks = svg.append("g").selectAll("g")
.data(chord.groups)
.enter().append("g").selectAll("g")
.data(groupTicks)
.enter().append("g")
.attr("transform", function(d) {
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ "translate(" + outerRadius + ",0)";
});
ticks.append("line")
.attr("x1", 1)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 0)
.style("stroke", "#000");
ticks.append("text")
.attr("x", 8)
.attr("dy", ".35em")
.attr("transform", function(d) { return d.angle > Math.PI ? "rotate(180)translate(-16)" : null; })
.style("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
.text(function(d) { return d.label; });
svg.append("g")
.attr("class", "chord")
.selectAll("path")
.data(chord.chords)
.enter().append("path")
.attr("d", d3.svg.chord().radius(innerRadius))
.style("fill", function(d) { return fill(d.target.index); })
.style("opacity", 1)
.on("mouseover", function(){
d3.select(this).style("opacity", 0.3);
})
.on("mouseout", function(){
d3.select(this).style("opacity", 1);
});
// Returns an array of tick angles and labels, given a group.
function groupTicks(d) {
var k = (d.endAngle - d.startAngle) / d.value;
return d3.range(0, d.value, 1000).map(function(v, i) {
return {
angle: v * k + d.startAngle,
label: i % 5 ? null : v / 1000 + "k"
};
});
}
// Returns an event handler for fading a given chord group.
function fade(opacity) {
return function(g, i) {
svg.selectAll(".chord path")
.filter(function(d) { return d.source.index != i && d.target.index != i; })
.transition()
.style("opacity", opacity);
};
}
body {
font: 10px sans-serif;
}
.chord path {
fill-opacity: .67;
stroke: #000;
stroke-width: .5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I've just added functionality for mouse over and mouse out,
svg.append("g")
.attr("class", "chord")
.selectAll("path")
.data(chord.chords)
.enter().append("path")
.attr("d", d3.svg.chord().radius(innerRadius))
.style("fill", function(d) { return fill(d.target.index); })
.style("opacity", 1)
.on("mouseover", function(){
d3.select(this).style("opacity", 0.3);
})
.on("mouseout", function(){
d3.select(this).style("opacity", 1);
});
In the above code see the mouseover and mouseout callbacks,
Here I'm just changing the opacity, if you want to change the color, use fill attribute to fill the color.
Hope you are looking for this.
If not ask me for more.
:D

D3 choropleth map not filling appropriately

I have a D3 map that should be filling US counties by how they fall within a range. I'm not having D3 determine that range for me, the data is such that 0, .5,1, and 10 work as quantiles.
The problem I'm having is that counties are not being filled with the appropriate color by where they are on the range. I know this because I can see the values via a tooltip. It's almost as if there's another value that is being cut into quantiles.
There is a complication in that I have a dropdown to select an additional variable to fill the map with (the same quantiles are fine for these variables)
Below is where I think my problem is, the initial code, the updateMap function or within the legend itself. Thank you for any suggestions.
var width = 800,
height = 500;
var statById = d3.map();
var quantile = d3.scale.quantile()
.domain([0,.5,1,10])
.range(['white','blue','red', 'green']);
function updateMap(key){
quantile.domain(counties.map(function(d){return d[key];}));
countyShapes
.transition().duration(1000).ease(d3.ease('linear'))
.attr("fill", function(d) {
if (statById.get(d.id)){
if(statById.get(d.id)[key] == 0){
return 'white';
}
else{
return quantile(statById.get(d.id)[key]);
}
}
else{
errorArray.push(d.id);
return "white";
}});
}
var legend = svg.selectAll('g.legendEntry')
.data(quantile.range().reverse())
.enter()
.append('g').attr('class', 'legendEntry');
legend
.append('rect')
.attr("x", width - 780)
.attr("y", function(d, i) {
return i * 20;
})
.attr("width", 10)
.attr("height", 10)
.style("stroke", "black")
.style("stroke-width", 1)
.style("fill", function(quantile){return quantile;});
//the data objects are the fill colors
legend
.append('text')
.attr("x", width - 760) //leave 5 pixel space after the <rect>
.attr("y", function(d, i) {
return i * 20;
})
.attr("dy", "0.8em") //place text one line *below* the x,y point
.text(function(d,i) {
var extent = quantile.invertExtent(d);
//extent will be a two-element array, format it however you want:
var format = d3.format("0.2f");
return format(+extent[0]) + " - " + format(+extent[1]);
});
And here is the entire script rendering the map:
<script>
var width = 800,
height = 500;
var statById = d3.map();
var quantile = d3.scale.quantile()
.domain([0,.5,1,10])
.range(['white','blue','red', 'green']);
var path = d3.geo.path();
var svg = d3.select("#map")
.attr("width", width)
.attr("height", height)
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw))
.append('svg:g');
svg.attr("transform", "scale( " + .9 + ")");
function redraw() {
console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
d3.select("#selectPer")
.on("change", function(){menuChange();});
d3.select("#selectType")
.on("change", function(){menuChange();});
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 1e-6)
.style("background", "rgba(250,250,250,.7)");
tooltip.append("span").attr("id", "countyName")
queue()
.defer(d3.json, "us.json")
.defer(d3.csv, "mvpfinal020715.csv")
.defer(d3.json, "countyPop.json")
.await(ready);
errorArray = [];
var counties;
var countyPop;
function ready(error, us, countiesJSON, countyPopJSON) {
counties = countiesJSON;
countyPop = countyPopJSON;
counties.forEach(function(d){
try{
d.lq_11 = +d['lq_11'];
d.lq_21 = +d['lq_21'];
d.lq_22 = +d['lq_22'];
d.lq_23 = +d['lq_23'];
d.lq_42 = +d['lq_42'];
d.lq_51 = +d['lq_51'];
d.lq_52 = +d['lq_52'];
d.lq_53 = +d['lq_53'];
d.lq_55 = +d['lq_55'];
d.lq_56 = +d['lq_56'];
d.lq_61 = +d['lq_61'];
d.lq_62 = +d['lq_62'];
d.lq_71 = +d['lq_71'];
d.lq_72 = +d['lq_72'];
d.lq_81 = +d['lq_81'];
d.lq_92 = +d['lq_92'];
statById.set(+d.fips, d);
if (isNaN('white')){
}
}
catch(e){
//remove double lines of csv
}
});
quantile.domain(counties.map(function(d){return d.subPerCap;}));
countyShapes = svg.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(us, us.objects.counties).features)
.enter().append("path")
countyShapes
.attr("fill", "rgb(200,200,200)")
.attr("d", path)
.on("mouseover", function(d){
d3.select(this)
.attr("stroke", "red")
.attr("stroke-width", 1)
tooltip
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 5) + "px")
.transition().duration(300)
.style("opacity", 1)
.style("display", "block")
updateDetails(statById.get(d.id));
})
.on("mouseout", function(d){
d3.select(this)
.attr("stroke", "")
.attr("stroke-width", .2)
tooltip.transition().duration(700).style("opacity", 0);
});
svg.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "states")
.attr("d", path);
menuChange();
}
var printDetails = [
{'var': 'fips', 'print': 'FIPS'},
{'var': 'lq_21', 'print': 'Mining'},
{'var': 'lq_23', 'print': 'Construction'},
{'var': 'lq_51', 'print': 'Information'},
{'var': 'lq_52', 'print': 'Finance'},
{'var': 'No Criminal Conviction Return', 'print': 'Paperwork Returns'},
{'var': 'none', 'print': ''},
{'var': 'population', 'print': 'Population'},
{'var': 'Activation Date', 'print': 'Start Date'}];
function updateDetails(county){
tooltip.selectAll("div").remove();
tooltip.selectAll("div").data(printDetails).enter()
.append("div")
.append('span')
.text(function(d){return (d.print.length > 0) ? d.print + ": " : " - ";})
.attr("class", "boldDetail")
.insert('span')
.text(function(d){
if (d.var != 'none'){
return (""+county[d.var]).indexOf('/') == -1 ? totalFormat(county[d.var]) : county[d.var];
}})
.attr("class", "normalDetail");
d3.select("#countyName").text(county.County);
}
var totalFormat = d3.format(",");
function menuChange(){
var selectPer = document.getElementById('selectPer');
selectPerValue = selectPer.options[selectPer.selectedIndex].value;
var selectType = document.getElementById('selectType');
selectTypeValue = selectType.options[selectType.selectedIndex].value;
var keyName = selectTypeValue + (selectPerValue == 'PerCap' ? 'PerCap' : '');
console.log(keyName);
updateMap(keyName);
console.log(d3.sum(counties, function(d){return d[selectTypeValue];}));
var num = d3.sum(counties, function(d){return d[selectTypeValue];});
d3.select("#magicNum")
.text(selectPerValue == 'PerCap' ? d3.round(num*10000/313000000, 3) + " per 100,000" : totalFormat(num));
}
function updateMap(key){
quantile.domain(counties.map(function(d){return d[key];}));
countyShapes
.transition().duration(1000).ease(d3.ease('linear'))
.attr("fill", function(d) {
if (statById.get(d.id)){
if(statById.get(d.id)[key] == 0){
return 'white';
}
else{
return quantile(statById.get(d.id)[key]);
}
}
else{
errorArray.push(d.id);
return "white";
}});
}
var legend = svg.selectAll('g.legendEntry')
.data(quantile.range().reverse())
.enter()
.append('g').attr('class', 'legendEntry');
legend
.append('rect')
.attr("x", width - 780)
.attr("y", function(d, i) {
return i * 20;
})
.attr("width", 10)
.attr("height", 10)
.style("stroke", "black")
.style("stroke-width", 1)
.style("fill", function(quantile){return quantile;});
//the data objects are the fill colors
legend
.append('text')
.attr("x", width - 760) //leave 5 pixel space after the <rect>
.attr("y", function(d, i) {
return i * 20;
})
.attr("dy", "0.8em") //place text one line *below* the x,y point
.text(function(d,i) {
var extent = quantile.invertExtent(d);
//extent will be a two-element array, format it however you want:
var format = d3.format("0.2f");
return format(+extent[0]) + " - " + format(+extent[1]);
});
</script>

D3 Pie Chart: Arctween in mouseOver doesn't work

Problem: The Arctween function will not work on the .on("mouseOver").
Intention: When hovering the arcs in the Pie Chart a highlight needs to start (opacity etc.) and information needs (infoHover) to show, next to the Arctween that I also want to activate.
I am aware that the code is not perfect at all, I'm just experimenting with d3.js.
Thanks in advance!
Javascript:
d3.json("dataExample.json", function (data) {
var width = 260,
height = 260;
var outerRadius = height / 2 - 20,
innerRadius = outerRadius / 3,
cornerRadius = 10;
colors = d3.scale.category20c();
var tempColor;
var pie = d3.layout.pie()
.padAngle(.02)
.value(function(d) {
return d.value;
})
var arc = d3.svg.arc()
.padRadius(outerRadius)
.innerRadius(innerRadius);
var infoHover = d3.select('#chart').append('div')
.style('position', 'absolute')
.style('padding', '0 30px')
.style('opacity', 0)
function arcTween(outerRadius, delay) {
return function() {
d3.select(this).transition().delay(delay).attrTween("d", function(d) {
var i = d3.interpolate(d.outerRadius, outerRadius);
return function(t) { d.outerRadius = i(t); return arc(d); };
});
};
}
var svg = d3.select("#chart").append("svg")
.data(data)
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.selectAll('path').data(pie(data))
.enter().append('path')
.attr('fill', function(d, i) {
return colors(i);
})
.each(function(d) { d.outerRadius = outerRadius - 20; })
.attr('d', arc)
.on("mouseover", function(d) {
infoHover.transition()
.style('opacity', .9)
.style('left', '85px')
.style('top', '120px')
infoHover.html(d.value + '%')
d3.selectAll("path")
.transition()
.duration(500)
.style("opacity", .18)
d3.select(this)
.transition()
.duration(500)
.style('opacity', 1)
.style('cursor', 'pointer')
arcTween(outerRadius, 0);
})
.on("mouseout", function(d) {
d3.selectAll("path")
.transition()
.duration(500)
.style("opacity", 1)
d3.select(this)
.style('opacity', 1)
arcTween(outerRadius - 20, 150);
});
});
Your arcTween returns a function your need to call:
arcTween(outerRadius, 0).call(this);

Categories