D3.js version3 scale.ordinal() not reading in my data the way I would like

Viewed 64

I am building a dot plot histogram with d3.js v3 and I am trying to color code my data based off one of the variables I read in from my csv file (it's city, which there's 10 of them). Problem is every dot is the same color when I run it in the browser.

Anyone know whats going on?

Heres a snapshot of my code:

var data = d3.csv('happy.csv', function(data) {

data.forEach(function(d) {
    d["index"] = d["index"];
    d["name"] = d["name"];
    d["city"] = d["city];
    ...
 })

var colorScheme = d3.scale.ordinal()
                .domain(data, (function(d) {
                    return d.city
                }))
                .range(["#FFADAF", "#FFC980", "#D9D9D9", "#9DACCB", "#95D1A5", 
                "#F586C6", "#BFDD81", "#F18489", "#80BCEC", "#FEFEA9"]);

...

var dots = groups.selectAll("circle")
.data(function(d) {
    return d3.range(1, +d.index + 1)
})
.enter().append("circle")
.attr("class", "dot")
.attr("r", 7.5)
.attr("cy", function(d) {
    return y(d)
})
.style("fill", function (d) { return colorScheme(d.city); } )

})
1 Answers

You bind each item in your data array to groups, and then use that data to create a data array to bind to child circles:

return d.range(1, +d.index + 1);

The problem is d3.range is just producing an array of numbers for the specified range. There is no city property for this bound datum, which is just a number: there's no trace of the original datum bound to the parent group carried forward as part of the datum bound to the child. Consequently, each time you try to color the circles by d.city you are passing undefined to the scale, as this is the only value passed to the scale, all circles are all the same color.

Instead you could use d3.range and array.map() to bind an array of objects to the children that carry forward the city property:

return d3.range(1, +d.index + 1).map(function(c) {
  return {city: d.city, index: c}
})

Now we have the city property carried forward to the circles:

var data = [
  {index: 5, city: "A"},
  {index: 4, city: "B"},
  {index: 6, city: "C"}
];

var colorScheme = d3.scale.ordinal()
                .domain(data, (function(d) {
                    return d.city
                }))
                .range(["#FFADAF", "#FFC980", "#D9D9D9", "#9DACCB", "#95D1A5", 
                "#F586C6", "#BFDD81", "#F18489", "#80BCEC", "#FEFEA9"]);

var svg = d3.select("body").append("svg");

var groups = svg.selectAll("g")
  .data(data)
  .enter()
  .append("g")
  .attr("transform",function(d,i) {
    return "translate("+[0,i*20]+")";
  })

var dots = groups.selectAll("circle")
.data(function(d) {
    return d3.range(1, +d.index + 1).map(function(c) {
      return {city: d.city, index: c}
    })
})
.enter().append("circle")
.attr("class", "dot")
.attr("r", 7.5)
.attr("cx", function(d,i) {
  return i*20+10;
})
.attr("cy", function(d,i) {
    return 60;
})
.style("fill", function (d) { return colorScheme(d.city); } )
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

Alternatively, we could access the parent datum with d3.select(this.parentNode).datum() when coloring the circles:

.style("fill", function (d) { return    colorScheme(d3.select(this.parentNode).datum().city); } )

As follows:

var data = [
  {index: 5, city: "A"},
  {index: 4, city: "B"},
  {index: 6, city: "C"}
];

var colorScheme = d3.scale.ordinal()
                .domain(data, (function(d) {
                    return d.city
                }))
                .range(["#FFADAF", "#FFC980", "#D9D9D9", "#9DACCB", "#95D1A5", 
                "#F586C6", "#BFDD81", "#F18489", "#80BCEC", "#FEFEA9"]);

var svg = d3.select("body").append("svg");

var groups = svg.selectAll("g")
  .data(data)
  .enter()
  .append("g")
  .attr("transform",function(d,i) {
    return "translate("+[0,i*20]+")";
  })

var dots = groups.selectAll("circle")
.data(function(d) {
    return d3.range(1, +d.index + 1)
})
.enter().append("circle")
.attr("class", "dot")
.attr("r", 7.5)
.attr("cx", function(d,i) {
  return i*20+10;
})
.attr("cy", function(d,i) {
    return 60;
})
.style("fill", function (d) { return colorScheme(d3.select(this.parentNode).datum().city); } )
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

Or, we could apply the fill at the level of the parent and skip applying a fill at all for the children:

var data = [
  {index: 5, city: "A"},
  {index: 4, city: "B"},
  {index: 6, city: "C"}
];

var colorScheme = d3.scale.ordinal()
                .domain(data, (function(d) {
                    return d.city
                }))
                .range(["#FFADAF", "#FFC980", "#D9D9D9", "#9DACCB", "#95D1A5", 
                "#F586C6", "#BFDD81", "#F18489", "#80BCEC", "#FEFEA9"]);

var svg = d3.select("body").append("svg");

var groups = svg.selectAll("g")
  .data(data)
  .enter()
  .append("g")
  .attr("transform",function(d,i) {
    return "translate("+[0,i*20]+")";
  })
  .attr("fill", function(d) { return colorScheme(d.city); })

var dots = groups.selectAll("circle")
.data(function(d) {
    return d3.range(1, +d.index + 1)
})
.enter().append("circle")
.attr("class", "dot")
.attr("r", 7.5)
.attr("cx", function(d,i) {
  return i*20+10;
})
.attr("cy", function(d,i) {
    return 60;
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>

Related