I want to draw a pie chart for every point on the map instead of a circle.
The map and the points are displaying well but the pie chart is not showing over the map points. There is no error also. I can see the added pie chart code inside map also.
Below is the code snippet .
var w = 600;
var h = 600;
var bounds = [[78,30], [87, 8]]; // rough extents of India
var proj = d3.geo.mercator()
.scale(800)
.translate([w/2,h/2])
.rotate([(bounds[0][0] + bounds[1][0]) / -2,
(bounds[0][1] + bounds[1][1]) / -2]); // rotate the project to bring India into view.
var path = d3.geo.path().projection(proj);
var map = d3.select("#chart").append("svg:svg")
.attr("width", w)
.attr("height", h);
var india = map.append("svg:g")
.attr("id", "india");
var gDataPoints = map.append("g"); // appended second
d3.json("data/states.json", function(json) {
india.selectAll("path")
.data(json.features)
.enter().append("path")
.attr("d", path);
});
d3.csv("data/water.csv", function(csv) {
console.log(JSON.stringify(csv))
gDataPoints.selectAll("circle")
.data(csv)
.enter()
.append("circle")
.attr("id", function (d,i) {
return "chart"+i;
})
.attr("cx", function (d) {
return proj([d.lon, d.lat])[0];
})
.attr("cy", function (d) {
return proj([d.lon, d.lat])[1];
})
.attr("r", function (d) {
return 3;
})
.each(function (d,i) {
barchart("chart"+i);
})
.style("fill", "red")
//.style("opacity", 1);
});
function barchart(id){
var data=[15,30,35,20];
var radius=30;
var color=d3.scale.category10()
var svg1=d3.select("#"+id)
.append("svg").attr('width',100).attr('height',100);
var group=svg1.append('g').attr("transform","translate(" + radius + "," + radius + ")");
var arc=d3.svg.arc()
.innerRadius('0')
.outerRadius(radius);
var pie=d3.layout.pie()
.value(function(d){
return d;
});
var arcs=group.selectAll(".arc")
.data(pie(data))
.enter()
.append('g')
.attr('class','arc')
arcs.append('path')
.attr('d',arc)
.attr("fill",function(d,i){
return color(d.data);
//return colors[i]
});
}
water.csv:
lon,lat,quality,complaints
80.06,20.07,4,17
72.822,18.968,2,62
77.216,28.613,5,49
92.79,87.208,4,3
87.208,21.813,1,12
77.589,12.987,2,54
16.320,75.724,4,7
In testing your code I was unable to see the pie charts rendering, at all. But, I believe I still have a solution for you.
You do not need a separate pie chart function to call on each point. I'm sure that there are a diversity of opinions on this, but d3 questions on Stack Overflow often invoke extra functions that lengthen code while under-utilizing d3's strengths and built in functionality.
Why do I feel this way in this case? It is hard to preserve the link between data bound to svg objects and your pie chart function, which is why you have to pass the id of the point to your function. This will be compounded if you want to have pie chart data in your csv itself.
With d3's databinding and selections, you can do everything you need with much simpler code. It took me some time to get the hang of how to do this, but it does make life easier once you get the hang of it.
Note: I apologize, I ported the code you've posted to d3v4, but I've included a link to the d3v3 code below, as well as d3v4, though in the snippets the only apparent change may be from color(i) to color[i]
In this case, rather than calling a function to append pie charts to each circle element with selection.each(), we can append a g element instead and then append elements directly to each g with selections.
Also, to make life easier, if we initially append each g element with a transform, we can use relative measurements to place items in each g, rather than finding out the absolute svg coordinates we would need otherwise.
d3.csv("water.csv", function(error, water) {
// Append one g element for each row in the csv and bind data to it:
var points = gDataPoints.selectAll("g")
.data(water)
.enter()
.append("g")
.attr("transform",function(d) { return "translate("+projection([d.lon,d.lat])+")" })
.attr("id", function (d,i) { return "chart"+i; })
.append("g").attr("class","pies");
// Add a circle to it if needed
points.append("circle")
.attr("r", 3)
.style("fill", "red");
// Select each g element we created, and fill it with pie chart:
var pies = points.selectAll(".pies")
.data(pie([0,15,30,35,20]))
.enter()
.append('g')
.attr('class','arc');
pies.append("path")
.attr('d',arc)
.attr("fill",function(d,i){
return color[i];
});
});
Now, what if we wanted to show data from the csv for each pie chart, and perhaps add a label. This is now done quite easily. In the csv, if there was a column labelled data, with values separated by a dash, and a column named label, we could easily adjust our code to show this new data:
d3.csv("water.csv", function(error, water) {
var points = gDataPoints.selectAll("g")
.data(water)
.enter()
.append("g")
.attr("transform",function(d) { return "translate("+projection([d.lon,d.lat])+")" })
.attr("class","pies")
points.append("text")
.attr("y", -radius - 5)
.text(function(d) { return d.label })
.style('text-anchor','middle');
var pies = points.selectAll(".pies")
.data(function(d) { return pie(d.data.split(['-'])); })
.enter()
.append('g')
.attr('class','arc');
pies.append("path")
.attr('d',arc)
.attr("fill",function(d,i){
return color[i];
});
});
The data we want to display is already bound to the initial g that we created for each row in the csv. Now all we have to do is append the elements we want to display and choose what properties of the bound data we want to show.
The result in this case looks like:
I've posted examples in v3 and v4 to show a potential implementation that follows the above approach for the pie charts:
With one static data array for all pie charts as in the example: v4 and v3
And by pulling data from the csv to display: v4 and v3
I would like to recreate something similar to the following examples:
http://bl.ocks.org/mbostock/3888852
http://bl.ocks.org/mbostock/1305111
The only difference is that I want to control the radius of each donut, rather than having it be the same for all of them. How do I dynamically vary the radius of the donut charts?
For this, you need to adjust the .innerRadius() and/or .outerRadius() dynamically for each appended pie chart, for example
svg.selectAll(".arc")
.data(function(d) { return pie(d.ages); })
.enter().append("path")
.attr("class", "arc")
.attr("d", function(d, i) { return arc.innerRadius(radius - 30 * Math.random())(d, i); })
.style("fill", function(d) { return color(d.data.name); });
Complete example here. In a real example, you'd want to specify the radius in the data and reference that instead of making up a random number for each segment of the pie chart. Then you can also have the same radius for all the segments in the same pie chart.
This question has been asked before but I don't think the given solution is the cleanest way to do it so I'm hoping someone may have figured it out since then. I am generating multiple pie charts using d3.js and am dynamically updating them through SQL queries. This is my update function:
function updateCharts()
{
var updatedDataSet = getDataSet();
// Create a pie layout and bind the new data to it
var layout = d3.layout.pie()
.value(function(d, i) { return d[i].count; })
.sort(null);
// Select the pie chart
var pieChartSVG = d3.selectAll("#pie");
// Select each slice of the pie chart
var arcsUpdate = pieChartSVG.selectAll("g.slice")
.data(layout([updatedDataSet]))
.enter();
// Apply transitions to the pie chart to reflect the new dataset
arcsUpdate.select("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.transition()
.duration(1000)
.attrTween("d", arcTween);
}
But it doesn't work. If I take the .enter() out of arcsUpdate then it works but applies the same changes(data and tweens) to each chart. I could get around this by doing a foreach() on the elements returned from pieChartSVG but I can't think of a way of doing that other than the one described in the other question.
I have had to use the solution from the other question as I have to move forward but it's not a "clean" solution so I'd love to know if anybody is aware of a better way to handle it.
I thought you need take the .enter() out of arcsUpdate just like
var arcsUpdate = pieChartSVG.selectAll("path")
.data(layout([updatedDataSet]));
// Apply transitions to the pie chart to reflect the new dataset
arcsUpdate.enter()
.append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.transition()
.duration(1000)
.attrTween("d", arcTween);
This is the correct way.
And if it applies the same changes(data and tweens) to each chart. Please check out they are binding same updateDataSet or not.
I've created a horizontal bar chart / row chart and am struggling to vertically align the Y-axis ticks with the center of my bars.
To create the Y-axis I'm using the following code:
var yScale = d3.scale.ordinal()
.domain(data.map(function (d) {
return d.subject;
}));
.rangeRoundBands([0, height]);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
And creating my bars like so:
// Render the bars
svg.selectAll('bar')
.data(data)
.enter()
.append('rect')
.attr('x', 1)
.attr('y', function (d) {
return yScale(d.subject);
})
.attr('width', function (d) {
return xScale(d.value)
})
.attr('height', barHeight)
.attr('fill', function (d, i) {
return color(i);
});
I'd like to be able to set the height of my chart to any value and have the ticks aligned correctly.
JSFiddle
The issue you are having is that you assigned an arbitrary barHeight value of 40 units.
When you use an ordinal scale with rangeRoundBands, d3 calculates a band size for you. You should use this value for your bar height. You can access the value that was calculated for your bands as ordinal.rangeBand, or in your specific case, yScale.rangeBand.
// instead of `barHeight`
.attr('height', yScale.rangeBand);
To avoid having all of your bars touching one another, you can give a second parameter to your rangeRoundBands method which is the padding between bands. Play around with the values for padding until you get the desired output.
Here's an updated JSFiddle
I have a stacked area chart like the one at : http://nvd3.org/ghpages/stackedArea.html
Is it possible to add a stroke to the top of the area bit of the plot so it looks like it has a border/stroke?
I tried adding in a stroke with the webkit inspector but nothing seems to happen (assuming this is like using .style('stroke','#000000')
So if there was just one series on the stackedAreaExample and it was blue in colour, the border would make it look something like this:
There's no border as such in SVG, so you have to add a rectangle that determines the border and assign the appropriate style. NVD3 doesn't have an option for this, but you can select the relevant element after it has been drawn and add the new content.
d3.select(".nv-stackedWrap")
.append("rect")
.attr("width", chart.xAxis.scale().range()[1])
.attr("height", chart.yAxis.scale().range()[0])
.style("fill", "none")
.style("stroke", "black");
This works for the stacked area chart; for other types of charts the name of the class of the element to select will be different.
Setting a border on the top area is tricker, as SVG doesn't allow you to set the stroke for only a single side of a path. You can do it with stroke-dasharray however -- you just need the total length of the path.
d3.select(".nv-area-" + (data.length-1))
.style("stroke", "black")
.style("stroke-opacity", 1)
.style("stroke-dasharray", function() {
return this.getTotalLength()/2 + "," + this.getTotalLength();
});
This selects the last (i.e. top) area and sets the stroke for it. The specified dasharray means that there will be a stroke for half of the path (i.e. the top) and then nothing for the length of the path (to make it appear as if there was only a stroke on the top).
The problem with this and NVD3 is the transition that "grows" the areas. If you run this code when the graph is created, the length of the line may be shorter than what it will be in the end. To make sure that the length is correct, you would need to (re)run this code after the transition is complete, e.g. using setTimeout.
Instead you can just draw a line chart with same data with darker color which will look like a border.
var areaFunc = d3.svg.area()
.interpolate("monotone")
.x(function(d) { return d.index; })
.y0(THIS.height)
.y1(function(d) { return d.delay });
var lineFunc = d3.svg.line()
.x(function(d) { return d.index; })
.y(function(d) { return d.delay });
.......
svg.append("path")
.datum(myData)
.attr("class", "area")
.attr("d", areaFunc);
svg.append("path")
.datum(myData)
.attr("class", "line") // with a darker color
.attr("d", lineFunc);