D3 adding two donut charts on top of each other. - javascript

I'm looking to somehow get two donut charts on top of one another, or atleast just the arcs. I want to hide one specific arc, and show the other on click, and then revert on click again.
I figured out you can simply hide an arc on click by selecting that slice, and doing d3.select("the arc").attr("visibility", "hidden");
So I want to hide one slice, and show the other. I want the arcs to take up the same spot, so showing the other appears to only change the arc.
Thank you,
Brian

As far as I understand your problem, you want to update a particular arc on click.
So, instead of creating two donuts, one on top of another, just create one donut chart and update it whenever the arc is clicked.
$(document).ready(function() {
var width = 400,
height = 250,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.value(function(d) {
return d.apples;
})
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 70)
.outerRadius(radius - 20);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var data = [{
"apples": 53245,
"oranges": 200
}, {
"apples": 28479,
"oranges": 200
}, {
"apples": 19697,
"oranges": 200
}, {
"apples": 24037,
"oranges": 200
}];
var path = svg.datum(data).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc)
.each(function(d) {
this._current = d;
}) // store the initial angles
.on("click", function(d) {
var key = d.data.getKeyByValue(d.value);
var oppKey = (key === "apples") ? "oranges" : "apples";
change(oppKey);
});
function change(keyVal) {
var value = keyVal;
pie.value(function(d) {
return d[value];
}); // change the value function
path = path.data(pie); // compute the new angles
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
function type(d) {
d.apples = +d.apples;
d.oranges = +d.oranges;
return d;
}
// Store the displayed angles in _current.
// Then, interpolate from _current to the new angles.
// During the transition, _current is updated in-place by d3.interpolate.
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
Object.prototype.getKeyByValue = function(value) {
for (var prop in this) {
if (this.hasOwnProperty(prop)) {
if (this[prop] === value)
return prop;
}
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Related

User Input with Range Sliders to generate D3js Donut Graph

Im trying to create a d3js donut graph based on a number of input range sliders.
Currently I have something thrown together from other peoples work - this:
var width = 500,
height = width,
radius = Math.min(width, height) / 2,
slices = 5,
range = d3.range(slices),
color = d3.scale.category10();
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d; });
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(radius/1.6);
// Set up the <svg>, then for each slice create a <g> and <path>.
var paths = d3.select("svg")
.attr({ width: width, height: height })
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.selectAll(".arc")
.data(range)
.enter()
.append("g")
.attr({ class : "arc", style: "stroke: #fff;" })
.append("path")
.style("fill", function(d, i) { return color(i); });
// Create as many input elements as there are slices.
var inputs = d3.select("form")
.selectAll(".field")
.data(range)
.enter()
.append("div")
.attr("class", "field")
.append("label")
.style("background", function (d) { return '#FFFFFF'; })
.append("input")
.attr({
type : "range",
min : 0,
max : 4000,
value : 1,
step : 1,
value : 500,
class : "range",
id : function(d, i) { return "v" + i; },
oninput : "update()"
});
// update() sets the <path>s to the pie slices that correspond
// to the slider values. It is called when the page loads and
// every time a slider is moved.
function getTotal () {
var total = 0;
d3.selectAll('.range').each(function () {
total = total + parseInt(this.value);
});
return total;
}
function showValues () {
d3.selectAll('.range').each(function () {
var perct = this.value + '%';
d3.select(this.parentNode.nextSibling).html(perct);
});
}
function update () {
var data = range.map(
function(i) { return document.getElementById("v" + i).value }
);
paths.data(pie(data)).attr("d", arc);
}
update();
I need to sent the value for and range for each input, ideal in the html markup, as well as display the value and label for each input.
I've been seeing a lot of different types of d3js graphs that come close to this, but I haven't seen on that really gets all these elements together.
This comes kinda close from this, but I also need the segments to update with the range slider.Thanks in advance
This is a big problem, and any help would be appreciated.

d3.js changing data on click (multiple instances of Donut Chart )

I'm trying to make a data visualisation with d3.js. Im new to d3 and i think i did not quite understand completely how to change data with a click, so i need your help!
Thats what i've got so far(which i'm quite happy that it works):
/*-------------------------data is parsed and proceedet above----------------------------------------------------*/
var SVGWidth = 1670;
var SVGeight = 800;
var kreis = 1;
var ringArea = 1;
var width = 1;
var multi = 3.5;
var color = d3.scale.ordinal().range(['#FFFFFF', '#DADAD9', '#9D9C9C']);
var arc = d3.svg.arc().innerRadius(function(d) {
donutWidth = Math.sqrt(d.data.WHI1 / Math.PI + Math.pow(d.data.E1, 2)) * multi - d.data.E1 * multi
return donutWidth;
}).outerRadius(function(d, i) {
width = Math.sqrt(d.data.WHI1 / Math.PI + Math.pow(d.data.E1, 2)) * multi
return width;
});
var pie = d3.layout.pie().value(function(d) {
return d.country;
}).sort(null);
var svg = d3.select('#chart')
.append('svg')
.attr('width', SVGWidth)
.attr('height', SVGeight)
.append("g")
svg.selectAll("g")
.data(data1995).
enter().append("g")
.attr({
transform: function(d, i) {
var pos = coord2Pt(geo[i][1], geo[i][2], 1.0);
return "translate(" + pos.x + ", " + pos.y + ")";
}
}).selectAll('path').data(function(country, i) {
return pie(country.map(function(value) {
return {
country: value,
WHI1: WHI1[i],
E1: E1[i]
};
}));
})
.enter()
.append('path')
.attr('d', function(d) {
return arc(d);
}).attr('fill', function(d, i) {
return color(i);
});
});
})(window.d3);
This results in this Visualisation:
Worldmap donut
What i'm now trying to do is to change my Data from "data1995" to "data2015", both "WHI1" to "WHI2" and both "E1" to "E2" with a click on the "test" button. In addition it would be great if they would change with a transition.
I'm not shure if I'm on the right path to accomplish this but is tried this so far:
d3.select("button").on('click', function() {
console.log("click")
d3.selectAll('path').transition().duration(500).attr("fill", function(d, i) {
return color(i);
}).attr("d", arc).each(function(d) {
this._current = d;
});
function change(data2015) {
return pie(country.map(function(value) {
return {
country: value,
WHI2: WHI2[i],
E2: E2[i]
};
}));
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
});
Thats what I've found on another thread on here (update d3 pie chart with new data.json)
but in fact i'm not even shure if i translated it right to my script and i'm curious if that would work with all of my instances of the Donut.
I suggest you to add a button with a onclick property, for example an update() function.
This function loads data2015 and change WHI1 to WHI2 and E1 to E2.
So this function is quite similar to what you have already written.
I think this approach is better because you do not need to update the chart as frequently as in the link you posted
I've found an example links, look at these:
http://www.d3noob.org/2013/02/update-d3js-data-dynamically-button.html
http://bl.ocks.org/d3noob/7030f35b72de721622b8

Creating a multilayer pie chart with D3

How can I create a multi layer pie chart with d3.js which looks like below
Every section doesn't have an inner subsection and when it has a subsection then it has darker color than the outer subsection as shown in the above image.
I tried searching for multilayer pie chart but what all I could do is this.
http://jsfiddle.net/ZpQ3x/
Here is corresponding javascript code
var dataset = {
final: [7000],
process: [1000, 1000, 1000, 7000],
initial: [10000],
};
var width = 660,
height = 500,
cwidth = 75;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("class","wrapper")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
var gs = svg.selectAll("g.wrapper").data(d3.values(dataset)).enter()
.append("g")
.attr("id",function(d,i){
return Object.keys(dataset)[i];
});
var gsLabels = svg.selectAll("g.wrapper").data(d3.values(dataset)).enter()
.append("g")
.attr("id",function(d,i){
return "label_" + Object.keys(dataset)[i];
});
var count = 0;
var path = gs.selectAll("path")
.data(function(d) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", function(d, i, j) {
d._tmp = d.endAngle;
d.endAngle = d.startAngle;
if(Object.keys(dataset)[j] === "final"){
d.arc = d3.svg.arc().innerRadius(cwidth*j).outerRadius(cwidth*(j+1));
}
else{
d.arc = d3.svg.arc().innerRadius(10+cwidth*j).outerRadius(cwidth*(j+1));
}
return d.arc(d);
})
.transition().delay(function(d, i, j) {
return i * 500;
}).duration(500)
.attrTween('d', function(d,x,y) {
var i = d3.interpolate(d.startAngle, d._tmp);
return function(t) {
d.endAngle = i(t);
return d.arc(d);
}
});
Thank you very much.
I have changed your dataset into a single JSON.
Just to ensure that mentioned above array x and x1 are related together i made data set like this.
data = [{
major: 100,//this is the X array first element
minor: 70,//this is the X1 array first element
grp: 1//here grp is for coloring the segment
}, {
major: 100,
minor: 30,
grp: 2
}, {
major: 100,
minor: 50,
grp: 3
}, {
major: 140,
minor: 70,
grp: 4
}, {
major: 80,
minor: 10,
grp: 5
}];
I have made two arc function.
var arcMajor = d3.svg.arc()
.outerRadius(function (d) {
return radius - 10;
})
.innerRadius(0);
//this for making the minor arc with variable radius as per scale
var arcMinor = d3.svg.arc()
.outerRadius(function (d) {
// scale for calculating the radius range([20, radius - 40])
return scale((d.data.major - d.data.minor));
})
This is the code which makes the path.
//this makes the major arc
g.append("path")
.attr("d", function (d) {
return arcMajor(d);
})
.style("fill", function (d) {
return d3.rgb(color(d.data.grp));
});
//this makes the minor arcs
g.append("path")
.attr("d", function (d) {
return arcMinor(d);
})
.style("fill", function (d) {
return d3.rgb(color(d.data.grp)).darker(2);//for making the inner path darker
});
Working code here with comments
Hope this helps!

d3 pie sort transition

How would I apply an animated transition when sorting a pie chart? I don't mean updating values, I mean sorting each arc of the pie to move into place the way bars do here.
There are plenty of transition examples for updating using new values of an existing data set (or switching data sets). I can't find anything on how to re-sort (using the same values, same data set).
I'm using this for now which simply redraws the arc by applying the same tween used when initializing the rendering, but it starts each arc from zero.
.attr('d', arc)
.transition()
.duration(1000)
.attrTween("d", tweenPie);
function tweenPie(b) {
var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
return function(t) { return arc(i(t)); };
};
Would I need to store the existing start and end angles somehow and run the tween from those?
I see something kind of like that here, though this example is updating values, not sorting.
Thanks.
Building on the Bostock example here.
setInterval(change, 2000);
var sort = false;
function change() {
sort = !sort;
if (sort){
pie = d3.layout.pie() //<-- pie with default sort
.value(function(d) {
return d.value;
});
} else {
pie = d3.layout.pie() //<-- pie with no sort
.value(function(d) {
return d.value;
})
.sort(null);
}
path = path.data(pie); // compute the new angles
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
Full code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 400px;
}
text {
font: 10px sans-serif;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<body>
<script>
var width = 400,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.value(function(d) {
return d.value;
})
.sort(null);
var defaultSort = pie.sort;
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 20);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var data = [{
value: 1
}, {
value: 5
}, {
value: 2
}, {
value: 6
}
];
var path = svg.datum(data).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc)
.each(function(d) {
this._current = d;
}); // store the initial angles
setInterval(change, 2000);
var sort = false;
function change() {
sort = !sort;
if (sort){
pie = d3.layout.pie()
.value(function(d) {
return d.value;
});
} else {
pie = d3.layout.pie()
.value(function(d) {
return d.value;
})
.sort(null);
}
path = path.data(pie); // compute the new angles
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
// Store the displayed angles in _current.
// Then, interpolate from _current to the new angles.
// During the transition, _current is updated in-place by d3.interpolate.
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
</script>
</body>

d3.js arc segment animation issue

I'm trying to create an animated arc segment with d3.js. I got the arc and the transition working, but while the animation is running, the arc gets distorted and I can't figure out why.
Here is what i have so far:
jsfiddle
var dataset = {
apples: [532, 284]
};
var degree = Math.PI/180;
var width = 460,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var pie = d3.layout.pie().startAngle(-90*degree).endAngle(90*degree)
.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.apples))
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc);
window.setInterval(dummyData, 2000);
function dummyData(){
var num = Math.round(Math.random() * 100);
var key = Math.floor(Math.random() * dataset.apples.length);
dataset.apples[key] = num;
draw();
};
function draw(){
svg.selectAll("path")
.data(pie(dataset.apples))
.transition()
.duration(2500)
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc);
}
As Richard explained, you're interpolating between the previously computed SVG path string and the newly computed string -- which is going to do some strange things -- rather than interpolating between the previous angle and the new angle, which is what you want.
You need to interpolate over the input and for each interpolated value map that to an SVG path string using your arc function. To do this, you need to store each previous datum somewhere and to use a custom tweening function, which you can find in examples in my previous comment.
1. Remember previous datum (initially):
.each(function(d) { this._current = d; });
2. Define a custom tweening function:
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0); // Remember previous datum for next time
return function(t) {
return arc(i(t));
};
}
3. Use it:
.attrTween("d", arcTween)
Here's what it looks like: http://jsfiddle.net/Qh9X5/18/

Categories