I modified Mike Bostock's Pie Chart Update III in order to get two different pie charts in one using two elements with one csv file for each chart.
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var leftG = svg.append("g")
.attr("transform", "translate(" + width / 4 + "," + height / 2 + ")");
var rightG = svg.append("g")
.attr("transform", "translate(" + 3 * (width / 4) + "," + height / 2 + ")");
I want to have each radio button updating both pie charts. Now my problem is that the update inputs only alter the second pie chart.
I can't figure out how to do this since the change() function is inside the d3.csv() function and one cannot access the other.
d3.selectAll("input")
.on("change", changeLeft);
function changeLeft() {
var value = this.value;
pieLeft.value(function(d) { return d[value]; }); // change the value function
pathLeft = pathLeft.data(pieLeft); // compute the new angles
pathLeft.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
Thanks in advance.
EDIT : Here is my plnkr
declare a global variable
var changeLeft;
Replace your function with the below one and it will work:
changeLeft = function() {
var value = this.value;
pieLeft.value(function(d) { return d[value]; }); // change the value function
pathLeft = pathLeft.data(pieLeft); // compute the new angles
pathLeft.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
Related
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.
I am brand new to d3.js and stackoverflow so please pardon if I ask something very basic. I have a basic donut chart however it is a modification of a normal donut chart since you can see there is one on top of another. I was wondering if it is possible to add a label right on the chart. I am able to add legend and label outside the chart but i want to be able to add a label right on the chart itself.
This is the code for chart
var dataset = {
data1: [53245, 28479, 19697, 24037, 40245],
data2: [53245, 28479, 19697, 24037, 40245]
};
var width = 460,
height = 300,
cwidth = 45;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
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) { return arc.innerRadius(10+cwidth*j).outerRadius(cwidth*(j+1))(d); });
This is the FIDDLE. I would highly be pleased with any suggestions to be able to add label and legend. I would want label to be on top of both the charts in the fiddle.
Is this what you're looking for? Fiddle.
Essentially what this does is append a text element which is positioned using the angle of the path element.
var angle = d.endAngle - d.startAngle;
var loc = d.startAngle - (Math.PI/2) + (angle/2);
return Math.sin(loc) * radius;
If you want the labels on the left side to not overlap onto the chart you could use something similar to the following
var textLength = [];
gEnter.selectAll("text").each(function () {
textLength.push(this.getComputedTextLength());
});
To get the lengths of the text objects, then you can modify either .attr("x", ...) or .attr("transform", "translate(x,0)") when d.startAngle is greater than PI (meaning that it is on the left side of the chart), subtracting the text length from the x element to position it further left.
hoping you can help point out my mistake here...
I can't get the second row of the legend to update on mouseover of the histogram:
I don't believe it's the selector because you can add an id attribute to both no problem, and the data seems to be coming through ok on a console.log() ...little bit baffled.
http://jsfiddle.net/2sgLjbvu/3/
var data = [
{Supplier:"Supplier A",val: {WIP:"500",Conv:"400"}},
{Supplier:"Supplier B",val: {WIP:"1400",Conv:"400"}},
{Supplier:"Supplier C",val: {WIP:"300",Conv:"800"}},
{Supplier:"Supplier D",val: {WIP:"1000",Conv:"200"}}
];
dashboard("#chartArea",data);
function dashboard(id,fData){
var barColor='steelblue';
function segColor(c){return {WIP:"#807dba",Conv:"#41ab5d"}[c];}
//compute total for each supplier
fData.forEach(function (d){d.total = parseFloat(d.val.WIP) + parseFloat(d.val.Conv);});
//func to handle histogram
function histoGram(fD){
//Dimensions
var hG={}, hGDim = {t:60,r:0,b:30,l:0};
hGDim.w = 500 - hGDim.l - hGDim.r,
hGDim.h = 300 - hGDim.t - hGDim.b;
//create svg for histogram.
var hGsvg = d3.select(id).append("svg")
.attr("width",hGDim.w +hGDim.l + hGDim.r)
.attr("height",hGDim.h + hGDim.t + hGDim.b)
.append("g")
.attr("transform","translate(" + hGDim.l + "," + hGDim.t + ")");
//create function for x-axis mapping
var x = d3.scale.ordinal().rangeRoundBands([0,hGDim.w],0.1)
.domain(fD.map(function(d){return d[0];}));
//Add x-axis to histogram
hGsvg.append("g")
.attr("class","x axis")
.attr("transform","translate(0," + hGDim.h + ")")
.call(d3.svg.axis().scale(x).orient("bottom"));
//create function for y-axis mapping
var y = d3.scale.linear().range([hGDim.h,0])
.domain([0,d3.max(fD,function(d){
return d[1];
})]);
//create bars for the histogram to contain recs and val labels
var bars = hGsvg.selectAll(".bar")
.data(fD)
.enter()
.append("g")
.attr("class","bar");
//create the recs
bars.append("rect")
.attr("x",function(d){return x(d[0]);})
.attr("y",function(d){return y(d[1]);})
.attr("width",x.rangeBand())
.attr("height",function(d){return hGDim.h - y(d[1]);})
.attr("fill",barColor)
.on("mouseover",mouseover) //defined below
.on("mouseout",mouseout); //defined below
function mouseover(d){
//filter for selected supplier
var st = fData.filter(function(s){return s.Supplier==d[0];})[0],
nD = d3.keys(st.val).map(function(s){return {type:s,val:st.val[s]};});
pC.update(nD);
leg.update(nD);
}
function mouseout(d){
//reset the pie-chart and legend
pC.update(tF);
leg.update(tF);
}
//func to update the bars - this will be used by the pie-chart
hG.update = function(nD,color){
//update domain of the y-axis to reflect change in supp
y.domain([0,d3.max(nD,function(d){return d[1];})]);
//attach the new data to the bars
var bars = hGsvg.selectAll(".bar")
.data(nD);
//transition of the height and color of rectangles
bars.select("rect")
.transition()
.duration(500)
.attr("y",function(d){return y(d[1]);})
.attr("height",function(d){return hGDim.h - y(d[1]);})
.attr("fill",color);
//transition the frequency labels loc
bars.select("text")
.transition()
.duration(500)
.text(function(d){ return d3.format(",")(d[1]);})
.attr("y",function(d){return y(d[1])-5;});
};
return hG;
}
function pieChart(pD){
var pC={},pieDim={w:250,h:250};
pieDim.r = Math.min(pieDim.w,pieDim.h)/2;
//create svg for pie chart
var piesvg = d3.select(id).append("svg")
.attr("width",pieDim.w)
.attr("height",pieDim.h)
.append("g")
.attr("transform","translate(" + pieDim.w/2 + "," + pieDim.h/2 + ")");
//create func to draw arcs of the slices
var arc = d3.svg.arc()
.outerRadius(pieDim.r-10)
.innerRadius(0);
var pie = d3.layout.pie()
.sort(null)
.value(function(d){return d.val;});
//Draw the pie slices
piesvg.selectAll("path")
.data(pie(pD))
.enter()
.append("path")
.attr("d",arc)
.each(function(d){this._current=d;})
.style("fill",function(d){return segColor(d.data.type);})
.on("mouseover",mouseover)
.on("mouseout",mouseout);
//create func to update pie-chart, this will be used by the histogram
pC.update = function(nD){
piesvg.selectAll("path")
.data(pie(nD))
.transition()
.duration(500)
.attrTween("d",arcTween);
};
//Utility func to be called on mouseover a pie slice
function mouseover(d){
//call the update function of the histogram with new data
hG.update(fData.map(function(v){
return[v.Supplier,v.val[d.data.type]];}),segColor(d.data.type));
}
//Utility func to be called on mouseout of a pie slice
function mouseout(d){
//call the update func of histogram with all data
hG.update(fData.map(function(v){
return[v.Supplier,v.total];}),barColor);
}
//Animate the pie-slice requiring a custom function which specifies
//how the intermeidate paths should be draw
function arcTween(a){
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t){return arc(i(t));};
}
return pC;
}
//func to handle legend
function legend(lD){
leg={};
//create table for legend
var lgend = d3.select(id)
.append("table")
.attr("class","legend");
//create one row per segment
var tr = lgend.append("tbody")
.selectAll("tr")
.data(lD)
.enter()
.append("tr");
//create the first column for each segment
tr.append("td")
.append("svg")
.attr("width","16")
.attr("height","16")
.append("rect")
.attr("width","16")
.attr("height","16")
.attr("fill",function(d){return segColor(d.type);});
//create the second column for each segment
tr.append("td")
.text(function(d){return d.type;});
//create the third col
tr.append("td")
.attr("class","legendFreq")
.text(function(d){return d3.format(",")(d.val);});
//create the perc col
tr.append("tr")
.attr("class","legendPerc")
.text(function(d){return getLegend(d,lD);});
//utility func to be used to update the legend
leg.update = function(nD){
var l = lgend.select("tbody")
.selectAll("tr")
.data(nD);
l.select(".legendFreq")
.text(function(d){return d3.format(",")(d.val);});
l.select(".legendPerc")
.text(function(d){return getLegend(d,nD);});
};
function getLegend(d,aD){//utility func to compute perc
return d3.format("%")(d.val/d3.sum(aD.map(function(v){return v.val;})));
}
return leg;
}
var sF = fData.map(function(d){
return [d.Supplier,d.total];
});
var tF = ['WIP','Conv'].map(function(d){
return {type:d,val:d3.sum(fData.map(function(t){return t.val[d];}))};
});
var hG = histoGram(sF),
pC=pieChart(tF),
leg = legend(tF);
}
Got it...
//create the perc col
tr.append("tr")
.attr("class","legendPerc")
.text(function(d){return getLegend(d,lD);});
should have been...
//create the perc col
tr.append("td")
.attr("class","legendPerc")
.text(function(d){return getLegend(d,lD);});
I am looking for an example for to rotate a pie chart on mouse down event. On mouse down, I need to rotate the pie chart either clock wise or anti clock wise direction.
If there is any example how to do this in D3.js, that will help me a lot. I found an example using FusionChart and I want to achieve the same using D3.js
Pretty easy with d3:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.age);
});
var curAngle = 0;
var interval = null;
svg.on("mousedown", function(d) {
interval = setInterval(goRotate,10);
});
svg.on("mouseup", function(d){
clearInterval(interval);
})
function goRotate() {
curAngle += 1;
svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ") rotate(" + curAngle + "," + 0 + "," + 0 + ")");
}
Working example.
I did a similar thing with a compass instead of pie chart. You mainly need three methods - each bound to a different mouse event.
Bind this to the mousedown event on your compass circle:
function beginCompassRotate(el) {
var rect = compassCircle[0][0].getBBox(); //compassCircle would be your piechart d3 object
compassMoving = true;
compassCenter = {
x: (rect.width / 2),
y: (rect.height / 2)
}
}
Bind this to the mouse move on your canvas or whatever is holding your pie chart - you can bind it to the circle (your pie chart) but it makes the movement a little glitchy. Binding it to the circle's container keeps it smooth.
function rotateCompass() {
if (compassMoving) {
var mouse = d3.mouse(svg[0][0]);
var p2 = {
x: mouse[0],
y: mouse[1]
};
var newAngle = getAngle(compassCenter, p2) + 90;
//again this v is your pie chart instead of compass
compass.attr("transform", "translate(90,90) rotate(" + newAngle + "," + 0 + "," + 0 + ")");
}
}
Finally bind this to the mouseup on your canvas - again you can bind it to the circle but this way you can end the rotation without the mouse over the circle. If it is on the circle you will keep rotating the circle until you have a mouse up event over the circle.
function endCompassRotate(el) {
compassMoving = false;
}
Here is a jsfiddle showing it working: http://jsfiddle.net/4oy2ggdt/
I am trying to build a bar graph that I can switch between the amount of data displayed based on a particular length of time. So far the code that I have is this,
var margin = {
top : 20,
right : 20,
bottom : 30,
left : 50
}, width = 960 - margin.left - margin.right, height = 500
- margin.top - margin.bottom;
var barGraph = function(json_data, type) {
if (type === 'month')
var barData = monthToArray(json_data);
else
var barData = dateToArray(json_data);
var y = d3.scale.linear().domain([ 0, Math.round(Math.max.apply(null,
Object.keys(barData).map(function(e) {
return barData[e]['Count']}))/100)*100 + 100]).range(
[ height, 0 ]);
var x = d3.scale.ordinal().rangeRoundBands([ 0, width ], .1)
.domain(d3.entries(barData).map(function(d) {
return barData[d.key].Date;
}));
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left");
var svg = d3.select("#chart").append("svg").attr("width",
width + margin.left + margin.right).attr("height",
height + margin.top + margin.bottom).append("g").attr(
"transform",
"translate(" + margin.left + "," + margin.top + ")");
svg.append("g").attr("class", "x axis").attr("transform",
"translate(0," + height + ")").call(xAxis);
svg.append("g").attr("class", "y axis").call(yAxis).append(
"text").attr("transform", "rotate(-90)").attr("y", 6)
.attr("dy", ".71em").style("text-anchor", "end").text(
"Total Hits");
svg.selectAll(".barComplete").data(d3.entries(barData)).enter()
.append("rect").attr("class", "barComplete").attr("x",
function(d) {
return x(barData[d.key].Date)
}).attr("width", x.rangeBand() / 2).attr("y",
function(d) {
return y(barData[d.key].Count);
}).attr("height", function(d) {
return height - y(barData[d.key].Count);
}).style("fill", "orange");
var bar = svg.selectAll(".barHits").data(d3.entries(barData))
.enter().append("rect").attr("class", "barHits").attr(
"x", function(d) {
return x(barData[d.key].Date) + x.rangeBand() / 2
}).attr("width", x.rangeBand() / 2).attr("y",
function(d) {
return y(barData[d.key].Count);
}).attr("height", function(d) {
return height - y(barData[d.key].Count);
}).style("fill", "red");
};
This does a great job displaying my original data set, I have a button set up to switch between the data sets which are all drawn at the beginning of the page. all of the arrays exist but no matter how hard I try I keep appending a new graph to the div so after the button click I have two graphs, I have tried replacing all of the code in the div, using the enter() exit() remove() functions but when using these I get an error stating that there is no exit() function I got this by following a post that someone else had posted here and another one here but to no avail. Any ideas guys?
What you are most likely doing is drawing a new graph probably with a new div each time the button is clicked, one thing you can try doing is to hold on to the charts container element somewhere, and when the button is clicked, you simply clear it's children and re-draw the graphs.
In practice, I almost never chain .data() and .enter().
// This is the syntax I prefer:
var join = svg.selectAll('rect').data(data)
var enter = join.enter()
/* For reference: */
// Selects all rects currently on the page
// #returns: selection
svg.selectAll('rect')
// Same as above but overwrites data of existing elements
// #returns: update selection
svg.selectAll('rect').data(data)
// Same as above, but now you can add rectangles not on the page
// #returns: enter selection
svg.selectAll('rect').data(data).enter()
Also important:
# selection.enter()
The enter selection merges into the update selection when you append or insert.
Okay, I figured it out So I will first direct all of your attention to here Where I found my answer. The key was in the way I was drawing the svg element, instead of replacing it with the new graph I was just continually drawing a new one and appending it to the #chart div. I found the answer that I directed you guys to and added this line in the beginning of my existing barGraph function.
d3.select("#barChart").select("svg").remove();
and it works like a charm, switches the graphs back and forth just as I imagined it would, now I have a few more tasks for the bargraph itself and I can move on to another project.
Thank you all for all of your help and maybe I can return the favor one day!