I am trying to make a horizontal bar chart for test purposes which changes data in real time. I notice that nodes keep adding.
var dataset = [ 5, 10, 15, 20, 25 ]
var w = 1200;
var h = 500;
var barPadding = 1;
var container = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.append("g");
var rects = container.selectAll("rect")
var yScale = d3.scaleLinear()
.range([h, 0])
function draw(dataset, translate){
yScale.domain([0, d3.max(dataset)])
rects.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i){
return i * 12 + translate
})
.attr("y", function(d){
return yScale(d)
})
.attr("width", 11)
.attr("height", function(d) { return (h - yScale(d)) })
rects.exit().remove()
}
var translate = 0
setInterval(function(){
container.attr("transform", "translate("+-translate+",0)")
dataset.push(Math.floor(Math.random() * 30))
draw(dataset, translate)
translate = translate + 12
dataset.shift()
}, 1000)
rects.exit.remove() doesn't seem to work, how can I fix this? I could not find any examples of live horizontal bar charts on d3 v5 which is what I am using here
Right now you don't have a proper update selection, which is:
var rects = container.selectAll("rect")
.data(dataset);
Because of that, all rectangles belong to the enter selection.
Here is the updated code, with the size of the update selection in the console:
var dataset = [5, 10, 15, 20, 25]
var w = 500;
var h = 300;
var barPadding = 1;
var container = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.append("g");
var yScale = d3.scaleLinear()
.range([h, 0]);
var translate = 0
draw(dataset, translate)
function draw(dataset, translate) {
yScale.domain([0, d3.max(dataset)])
var rects = container.selectAll("rect")
.data(dataset);
rects.enter()
.append("rect")
.merge(rects)
.attr("x", function(d, i) {
return i * 12 + translate
})
.attr("y", function(d) {
return yScale(d)
})
.attr("width", 11)
.attr("height", function(d) {
return (h - yScale(d))
})
rects.exit().remove();
console.log("the update size is: " + rects.size())
}
setInterval(function() {
container.attr("transform", "translate(" + -translate + ",0)")
dataset.push(Math.floor(Math.random() * 30))
draw(dataset, translate)
translate = translate + 12
dataset.shift()
}, 1000)
<script src="https://d3js.org/d3.v5.min.js"></script>
Related
I am very new to D3 and as you can see in the image above there are tiny lines/gaps between each rectangle that I would love to get rid of, this is drawn on a canvas element with each rectangle starting where the last one ends using D3.js following this tutorial almost exactly minus adding the gaps between each square.
I've tried
this.canvas.imageSmoothingQuality = 'low';
draw() {
const canvas = d3
.select(this.chartContainer.nativeElement)
.append('canvas')
.attr('width', this.width)
.attr('height', this.height)
.attr(
'transform',
'translate(' + this.margin.left + ',' + this.margin.top + ')'
);
this.canvas = canvas.node().getContext('2d');
this.clearCanvas();
this.canvas.imageSmoothingQuality = 'low';
const elements = this.shadowContainer.selectAll('custom.rect');
const _this = this;
elements.each(function(d, i) {
const node = d3.select(this);
// Here you retrieve the colour from the individual in-memory node and set the fillStyle for the canvas paint
_this.canvas.fillStyle = node.attr('color');
// Here you retrieve the position of the node and apply it to the fillRect context function which will fill and paint the square.
_this.canvas.fillRect(
Number(node.attr('x')),
Number(node.attr('y')),
Number(node.attr('width')),
Number(node.attr('height'))
);
});
}
private dataBind(value) {
const customBase = document.createElement('custom');
this.shadowContainer = d3.select(customBase);
const {
viewModes: {
heatMap: {
data,
chartOptions: { engagementStatus, xAxis, yAxis }
}
}
} = value;
const x = this.d3
.scaleBand()
.range([0, this.width])
.domain(xAxis.categories);
this.shadowContainer
.append('g')
.style('font-size', 11)
.attr('class', 'x-axis')
.call(this.d3.axisTop(x).tickSize(0))
.select('.domain')
.remove();
this.shadowContainer
.selectAll('.x-axis text')
.style('text-anchor', 'start')
.attr('transform', function(d) {
return `translate(8, -8)rotate(-90)`;
});
const y = this.d3
.scaleBand()
.domain(d3.reverse(yAxis.categories))
.range([this.height, 0]);
const color = this.d3
.scaleLinear()
.domain([-2, -1, 0, 1])
// #ts-ignore
.range(['#5b717d', '#ffb957', '#ee6b56', '#40a050']);
const join = this.shadowContainer
.selectAll('custom.rect')
.data(data, function(d) {
return `${d.Date}:${d.Member}`;
});
const enterSelection = join
.enter()
.append('custom')
.attr('class', 'rect')
.attr('x', d =>
this.getCorrectDatePosition(
d.Date,
x,
xAxis.categories[0].split('/').length
)
)
.attr('y', function(d) {
return y(d.Member);
})
.attr('width', 24)
.attr('height', 24);
join
.merge(enterSelection)
.attr('width', x.bandwidth())
.attr('height', y.bandwidth())
.attr('color', function(d) {
return color(d.score);
});
const exitSelection = join
.exit()
.transition()
.attr('width', 0)
.attr('height', 0)
.remove();
}
This is likely an issue stemming from your scales. It can occur with either SVG or canvas and occurs when dealing with coordinates that require plotting at fractions of a pixel.
Here's a demonstration with SVG:
var data = d3.range(20);
var x = d3.scaleBand()
.range([10,250])
.domain(data)
var svg = d3.select("body")
.append("svg")
.attr("width", 500);
var rect = svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", d=>x(d) )
.attr("y", 50)
.attr("width", x.bandwidth())
.attr("height",100)
.attr("fill","crimson")
svg.transition()
.attrTween("tween", function() {
var i = d3.interpolate(250,480)
return function(t) {
x.range([50,i(t)])
rect.attr("x",d=>x(d))
.attr("width", x.bandwidth());
return "";
}
})
.duration(10000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
And one with Canvas:
var data = d3.range(20);
var x = d3.scaleBand()
.range([10,250])
.domain(data)
var canvas = d3.select("body")
.append("canvas")
.attr("width", 500);
var rect = d3.create("div").selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", d=>x(d) )
.attr("y", 50)
.attr("width", x.bandwidth())
.attr("height",100)
.attr("fill","crimson")
canvas.transition()
.attrTween("tween", function() {
var i = d3.interpolate(250,480)
var context = canvas.node().getContext("2d");
return function(t) {
x.range([50,i(t)])
context.fillStyle = "#fff";
context.fillRect(0,0,550,300);
rect.attr("x",d=>x(d))
.attr("width", x.bandwidth())
.each(function() {
var node = d3.select(this);
context.fillStyle = "crimson"
context.fillRect(
+node.attr("x"),
+node.attr("y"),
+node.attr("width"),
+node.attr("height"))
})
return "";
}
})
.duration(10000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
The solution is to be a bit more involved in setting the scale's domain and range. Start with the desired bandwidth, a whole number in pixels, and set the range so that the difference between the minimum and maximum values is equal to the number of values in the domain * the bandwidth.
So instead of:
const x = this.d3
.scaleBand()
.range([0, this.width])
.domain(xAxis.categories);
You'd have:
const length = 10; // length of a box side
const x = this.d3
.scaleBand()
.domain(xAxis.categories)
.range([0,xAxis.categories * length])
You could also calculate length above dynamically, say by using: Math.floor(width/xAxis.categories)
Using the above approach and a slightly contrived example to accommodate the transition, we remove the aliasing/moire pattern. Because we use only full pixels, the transition jumps as each bar increases in width by a full pixel at the same time, as space becomes available in the range:
var data = d3.range(20);
var length = 30;
var x = d3.scaleBand()
.range([10,data.length*length])
.domain(data)
var canvas = d3.select("body")
.append("canvas")
.attr("width", 500);
var rect = d3.create("div").selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", d=>x(d) )
.attr("y", 50)
.attr("width", x.bandwidth())
.attr("height",100)
.attr("fill","crimson")
canvas.transition()
.attrTween("tween", function() {
var i = d3.interpolate(250,480)
var context = canvas.node().getContext("2d");
return function(t) {
length = Math.floor(i(t)/data.length)
x.range([10,length*data.length+10])
context.fillStyle = "#fff";
context.fillRect(0,0,550,300);
rect.attr("x",d=>x(d))
.attr("width", x.bandwidth())
.each(function(d,i) {
var node = d3.select(this);
context.fillStyle = d3.schemeCategory10[i%10];
context.fillRect(
+node.attr("x"),
+node.attr("y"),
+node.attr("width"),
+node.attr("height"))
})
return "";
}
})
.duration(10000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
I'm following the D3 tutorial but adding the axis makes half of my data disappear and I don't understand why. I thought that maybe the axis is taking up the space that's meant for the data so I added an extra 10px to the transform property, but it doesn't make any difference.
var GIST = "https://gist.githubusercontent.com/charisseysabel/f8f48fbf11b8a1b0d62cbe2d6bdc2aa6/raw/2ead1537adb822fbd59a666afd5334d525480a13/nano-2017.tsv"
var width = 1000,
height = 550,
margin = {top: 20, right: 30, bottom: 30, left: 4};
var y = d3.scaleLinear()
.range([height, 0]);
var yScale = d3.scaleLinear()
.range([height, 0]);
var xScale = d3.scaleLinear()
.range([0, width]);
var xAxis = d3.axisLeft(yScale);
var yAxis = d3.axisBottom(xScale);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
chart.append("g")
.attr("transform", "translate(10, 0)")
.call(xAxis);
chart.append("g")
.attr("transform", "translate(0, 540)")
.call(yAxis);
d3.tsv(GIST, type, function(error, data) {
y.domain([0, d3.max(data, function(d) { return d.value; })]);
var barWidth = width / data.length;
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(" + ((i * barWidth) + 10) + ",0)"; }
);
bar.append("rect")
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("width", barWidth - 1);
bar.append("text")
.attr("x", (barWidth / 2) - 2)
.attr("y", function(d) { return y(d.value) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.value; });
});
function type(d) {
d.value = +d.value;
return d;
}
When you do this...
var bar = chart.selectAll("g").etc...
... you're selecting group elements that already exist in the SVG, which are the axes, and binding your data to them.
There are two easy solutions:
Move your code that creates the axes to the bottom of the d3.tsv, that is, after you have appended the bars.
Select something that doesn't exist, like
var bar = chart.selectAll(null).etc. To read more about the logic behind selectAll(null), have a look at my answer here.
In the code below, a simple pie chart is created, but I am not able to move one slice towards the outer side of the chart when selected.
I want the individual (element) slice to be positioned outer the pie and the rest of the pie chart elements(slices) in its usual position, something like this:
Here is my code:
<!DOCTYPE html>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data = [35, 20, 45];
var width = 300,
height = 300,
radius = 150;
var arc = d3.arc()
.outerRadius(130);
var arcLabel = d3.arc()
.outerRadius(radius - 30)
.innerRadius(radius - 20);
var pie = d3.pie()
.value(function(d) {
return d;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var emptyPies = svg.selectAll(".arc")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
emptyPies.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
return color[i];
})
emptyPies.append("text")
.attr("transform", function(d) {
return "translate(" + arcLabel.centroid(d) + ")";
})
.text(function(d) {
return d.data;
});
</script>
A simple solution is creating a different arc generator:
var arc2 = d3.arc()
.outerRadius(radius)
.innerRadius(60);
And, when setting the "d" attribute, choosing which arc generator to use. For instance, moving the red slice:
emptyPies.append("path")
.attr("d", function(d,i){
return i != 1 ? arc(d) : arc2(d);
})
Here is your code with that change:
<!DOCTYPE html>
<style>
.arc text {
text-anchor: middle;
}
.arc path {
stroke: white;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data = [35, 20, 45];
var width = 300,
height = 300,
radius = Math.min(width, height) / 2;
var color = ["brown", "red", "blue"];
var arc = d3.arc()
.outerRadius(radius - 10)
.innerRadius(50);
var arc2 = d3.arc()
.outerRadius(radius)
.innerRadius(60);
var arcLabel = d3.arc()
.outerRadius(radius - 30)
.innerRadius(radius - 20);
var pie = d3.pie()
.value(function(d) {
return d;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var emptyPies = svg.selectAll(".arc")
.data(pie(data))
.enter()
.append("g")
.attr("class", "arc")
emptyPies.append("path")
.attr("d", function(d,i){
return i != 1 ? arc(d) : arc2(d);})
.style("fill", function(d, i) {
return color[i];
})
emptyPies.append("text")
.attr("transform", function(d) {
return "translate(" + arcLabel.centroid(d) + ")";
})
.text(function(d) {
return d.data;
});
</script>
A simple solution is to use multiple arc() but to do slice we can use arc.centroid() of 2nd arc. The following code will work in v5.
function onDrawPieChart() {
var data = [35, 20, 45];
var color = d3.schemeCategory10;
var width = 600;
var height = 600;
var radius = 100;
var pie = d3.pie().value((d) => d);
var arc = d3.arc().innerRadius(0).outerRadius(130);
var arc2 = d3.arc().innerRadius(0).outerRadius(20);
var slicedIndex = 1;
var pieData = pie(data);
var centroid = arc2.centroid(pieData[slicedIndex]);
var svg = d3
.select("body")
.append("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height].join(" "))
.attr("width", width)
.attr("height", height)
.append("g");
svg
.selectAll("path")
.data(pieData)
.join("path")
.attr("fill", (d, i) => color[i])
.attr("d", (d) => arc(d))
.attr("transform", (d, i) => {
if (i === slicedIndex) {
var [x, y] = centroid;
return "translate(" + x + ", " + y + ")";
}
});
}
I've been looking at this example of a beeswarm plot in d3.js and I'm trying to figure out how to change the size of the dots and without getting the circles to overlap. It seems if the radius of the dots change, it doesn't take this into account when running the calculations of where to place the dots.
This is a cool visualization.
I've made a plunk of it here: https://plnkr.co/edit/VwyXfbc94oXp6kXQ7JFx?p=preview and modified it to work a bit more like you're looking for (I think). The real key is changing the call to handle collision to vary based on the radius of the circles (in the original post it's hard coded to 4, which works well when r === 3 but fails as r grows). The changes:
Make the circle radius into a variable (line 7 of script.js, var r = 3;)
Change the d3.forceCollide call to use that radius and a multiplier - line 110 (.force("collide", d3.forceCollide(r * 1.333)))
Change the .enter() call to use that radius as well (line 130: .attr("r", r))
This works reasonably well for reasonable values of r - but you'll need to adjust the height, and it might even be nice to just change the whole thing so that r is based on height (e.g. var r = height * .01). You'll notice that as is now, the circles go off the bottom and top of the graph area.
This post might be of interest as well: Conflict between d3.forceCollide() and d3.forceX/Y() with high strength() value
Here's the whole of script.js for posterity:
var w = 1000, h = 280;
var padding = [0, 40, 34, 40];
var r = 5;
var xScale = d3.scaleLinear()
.range([ padding[3], w - padding[1] ]);
var xAxis = d3.axisBottom(xScale)
.ticks(10, ".0s")
.tickSizeOuter(0);
var colors = d3.scaleOrdinal()
.domain(["asia", "africa", "northAmerica", "europe", "southAmerica", "oceania"])
.range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33']);
d3.select("#africaColor").style("color", colors("africa"));
d3.select("#namericaColor").style("color", colors("northAmerica"));
d3.select("#samericaColor").style("color", colors("southAmerica"));
d3.select("#asiaColor").style("color", colors("asia"));
d3.select("#europeColor").style("color", colors("europe"));
d3.select("#oceaniaColor").style("color", colors("oceania"));
var formatNumber = d3.format(",");
var tt = d3.select("#svganchor").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select("#svganchor")
.append("svg")
.attr("width", w)
.attr("height", h);
var xline = svg.append("line")
.attr("stroke", "gray")
.attr("stroke-dasharray", "1,2");
var chartState = {};
chartState.variable = "totalEmission";
chartState.scale = "scaleLinear";
chartState.legend = "Total emissions, in kilotonnes";
d3.csv("co2bee.csv", function(error, data) {
if (error) throw error;
var dataSet = data;
xScale.domain(d3.extent(data, function(d) { return +d.totalEmission; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding[2]) + ")")
.call(xAxis);
var legend = svg.append("text")
.attr("text-anchor", "middle")
.attr("x", w / 2)
.attr("y", h - 4)
.attr("font-family", "PT Sans")
.attr("font-size", 12)
.attr("fill", "darkslategray")
.attr("fill-opacity", 1)
.attr("class", "legend");
redraw(chartState.variable);
d3.selectAll(".button1").on("click", function(){
var thisClicked = this.value;
chartState.variable = thisClicked;
if (thisClicked == "totalEmission"){
chartState.legend = "Total emissions, in kilotonnes";
}
if (thisClicked == "emissionPerCap"){
chartState.legend = "Per Capita emissions, in metric tons";
}
redraw(chartState.variable);
});
d3.selectAll(".button2").on("click", function(){
var thisClicked = this.value;
chartState.scale = thisClicked;
redraw(chartState.variable);
});
d3.selectAll("input").on("change", filter);
function redraw(variable){
if (chartState.scale == "scaleLinear"){ xScale = d3.scaleLinear().range([ padding[3], w - padding[1] ]);}
if (chartState.scale == "scaleLog"){ xScale = d3.scaleLog().range([ padding[3], w - padding[1] ]);}
xScale.domain(d3.extent(dataSet, function(d) { return +d[variable]; }));
var xAxis = d3.axisBottom(xScale)
.ticks(10, ".0s")
.tickSizeOuter(0);
d3.transition(svg).select(".x.axis").transition().duration(1000)
.call(xAxis);
var simulation = d3.forceSimulation(dataSet)
.force("x", d3.forceX(function(d) { return xScale(+d[variable]); }).strength(2))
.force("y", d3.forceY((h / 2)-padding[2]/2))
.force("collide", d3.forceCollide(r * 1.333))
.stop();
for (var i = 0; i < dataSet.length; ++i) simulation.tick();
var countriesCircles = svg.selectAll(".countries")
.data(dataSet, function(d) { return d.countryCode});
countriesCircles.exit()
.transition()
.duration(1000)
.attr("cx", 0)
.attr("cy", (h / 2)-padding[2]/2)
.remove();
countriesCircles.enter()
.append("circle")
.attr("class", "countries")
.attr("cx", 0)
.attr("cy", (h / 2)-padding[2]/2)
.attr("r", r)
.attr("fill", function(d){ return colors(d.continent)})
.merge(countriesCircles)
.transition()
.duration(2000)
.attr("cx", function(d) { console.log(d); return d.x; })
.attr("cy", function(d) { return d.y; });
legend.text(chartState.legend);
d3.selectAll(".countries").on("mousemove", function(d) {
tt.html("Country: <strong>" + d.countryName + "</strong><br>"
+ chartState.legend.slice(0, chartState.legend.indexOf(",")) + ": <strong>" + formatNumber(d[variable]) + "</strong>" + chartState.legend.slice(chartState.legend.lastIndexOf(" ")))
.style('top', d3.event.pageY - 12 + 'px')
.style('left', d3.event.pageX + 25 + 'px')
.style("opacity", 0.9);
xline.attr("x1", d3.select(this).attr("cx"))
.attr("y1", d3.select(this).attr("cy"))
.attr("y2", (h - padding[2]))
.attr("x2", d3.select(this).attr("cx"))
.attr("opacity", 1);
}).on("mouseout", function(d) {
tt.style("opacity", 0);
xline.attr("opacity", 0);
});
d3.selectAll(".x.axis, .legend").on("mousemove", function(){
tt.html("This axis uses SI prefixes:<br>m: 10<sup>-3</sup><br>k: 10<sup>3</sup><br>M: 10<sup>6</sup>")
.style('top', d3.event.pageY - 12 + 'px')
.style('left', d3.event.pageX + 25 + 'px')
.style("opacity", 0.9);
}).on("mouseout", function(d) {
tt.style("opacity", 0);
});
//end of redraw
}
function filter(){
function getCheckedBoxes(chkboxName) {
var checkboxes = document.getElementsByName(chkboxName);
var checkboxesChecked = [];
for (var i=0; i<checkboxes.length; i++) {
if (checkboxes[i].checked) {
checkboxesChecked.push(checkboxes[i].defaultValue);
}
}
return checkboxesChecked.length > 0 ? checkboxesChecked : null;
}
var checkedBoxes = getCheckedBoxes("continent");
var newData = [];
if (checkedBoxes == null){
dataSet = newData;
redraw();
return;
};
for (var i = 0; i < checkedBoxes.length; i++){
var newArray = data.filter(function(d){
return d.continent == checkedBoxes[i];
});
Array.prototype.push.apply(newData, newArray);
}
dataSet = newData;
redraw(chartState.variable);
//end of filter
}
//end of d3.csv
});
I need different colors for 3 graph which is shows in black color.
But rest parts color will be same for three.
How to solve that issue.
Code here:
var dataset = {
apples: [33, 70],
oranges: [12, 80],
lemons: [20, 90],
};
var width = 660,
height = 500,
cwidth = 35;
var color = d3.scale.ordinal().range(["#000000", "#f5f5f5"]);//Colors
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 / 3 + "," + height / 3 + ")");
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function(d) { return pie(d); })
.style("fill", function(d) { return color(d.pie); })//Returning color from here
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", function(d, i, j) { return arc.innerRadius(4+cwidth*j).outerRadius(cwidth* (j+1))(d); });
This will work
var i=0;
var RamdomColor=function(col){
var colorsArray = ['#f5f5f5', 'red', 'blue', 'green'];
if(col == 0){
++i
return colorsArray[i];
}
else
return colorsArray[0];
}
var gs = svg.selectAll("g").data(d3.values(dataset)).enter().append("g");
var path = gs.selectAll("path")
.data(function(d,i) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i) { return RamdomColor(i);})
.attr("transform", "translate(100,100) rotate(270 50 50)")
.attr("d", function(d, i,j) { return arc.innerRadius(4+cwidth*j).outerRadius(cwidth* (j+1))(d); });
I've turned you code into this jsbin. There were a number of things wrong with it and these are commented in the code. I've also tidied up the code a bit it was difficult to read.
The reason you were getting black circles was because you were trying to get a colour with an undefined attribute in this line style("fill", function(d) { return color(d.pie); }). You were also only had 2 colours defined in var color = d3.scale.ordinal().range(["#000000", "#f5f5f5"]); so it would have been impossible to get three colours.