D3.js v3 Dot Plot Histogram Duplicating Values

Viewed 299

I am building a dot plot histogram with d3.js v3 and I have pretty much finished everything up - except for whatever reason some of my data points are duplicating (certain circles repeating themselves - not all of them, just some). I tried tweaking the axis parameters, as well as the data itself [deleted rows with null values, etc]- however sadly to no avail.

Any help would be immensely appreciated.

Here's my relevant code:

<div id="dotHappy"></div>

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

 data.forEach(function(d) {
    d["city"] = d["city"];
    d["Happy"] = +d["Happy"];
    d["thc"] = +d["thc"];
 });

 var margin = {
    top: 30,
    right: 20,
    bottom: 30,
    left: 50
},
width = 1560 - margin.left - margin.right,
height = 1260 - margin.top - margin.bottom;

I tried this coder block but it wasn't working. (Not sure if this is even what's giving me the issue anyways - perhaps not).

// var x = d3.scale.linear()
//     .range([0, width]);

So I went with this:

var x = d3.scale.ordinal()
.rangePoints([0, width])

var y = d3.scale.linear()
.range([height, 0]);

var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");

var yAxis = d3.svg.axis()
.scale(y)
.orient("left");

var svg = d3.select("#dotHappy")
 .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 + ")");

var chart = svg.append("g")
 .attr("id", "chart");

Also tried tweaking this, which may or may not even be part of the problem.

x.domain(data.map(d => d.Happy));
y.domain([5, 33]);
// y.domain(data.map(d => d.city));

svg.append("g")
 .attr("class", "x axis")
 .attr("transform", "translate(0," + height + ")")
 .call(xAxis)
 // .append("text")
 .attr("class", "label")
 .attr("x", width)
 .attr("y", -6)
 .style("text-anchor", "end")
 .text("Happy");

svg.append("g")
 .attr("class", "y axis")
 // .attr("transform", "translate(0," + width + ")")
 .call(yAxis)
 // .append("text")
 .attr("class", "label")
 .attr("transform", "rotate(-90)")
 .attr("y", 6)
 .attr("dy", ".71em")
 .style("text-anchor", "end")
 .text("THC");

var groups = svg.selectAll(".groups")
 .data(data)
 .enter()
 .append("g")
 .attr("transform", function(d) {
    return "translate(" + x(d.Happy) + ".0)";
 });

var dots = groups.selectAll("circle")
.data(function(d) {
    return d3.range(1, +d.thc + 1)
    // return d3.range(d.thc)
})
.enter().append("circle")
.transition().duration(1000)
.attr("class", "dot")
.attr("r", 10)
.attr("cy", function(d) {
    return y(d)
})
.style("fill", "blue")
.style("opacity", 1);

})

Here is a snapshot of my csv file:

city.  |.   Happy.    | thc
Boston.       37.        23
NYC.          22.        30
Chicago.      88.        5

Following is a screenshot of what it currently looks like. So in this case, the tooltip displaying the text box 'The Sister' should be only for one circle (because it should only be one data point), however if you hover over the other 10 orange circles below it, it's all the same - indicating it has been repeated 11 times total:

repeated circles in visualization

1 Answers

Actually, all of the circles are repeating vertically. You may not see them all because the repeated circles are being overlapped by other colored circles as these other circles get drawn. For example, the yellow data point "The Sister" is repeating all the way down to the bottom, but the data points below the yellow ones, in blue, pink, green, blue, etc., drew themselves on top of the yellow repeats.

The culprit is this code:

.selectAll("circle")
.data(function(d) {
    return d3.range(1, +d.thc + 1)
    // return d3.range(d.thc)
})
.enter().append("circle")

which, if you don't want it to repeat, should have been just one line:

.append("circle")

To explain what happened, this code:

var groups = svg.selectAll(".groups")
 .data(data)
 .enter()
 .append("g")
 .attr("class", "groups") //NOTE: you should add this line since you have 'selectAll(".groups")'
 .attr("transform", function(d) {
    return "translate(" + x(d.Happy) + ".0)";
 });

already creates a g element for every row in the csv file. And for every g, you created an array using d3.range(1, +d.thc + 1), and appended a circle for each item in that array. As an example, let's take the row representing "The Sister" data point that has a THC of 33. For that one data point, the code creates one <g>, inside of which it binds the array [1, 2, 3, ..., 33], and therefore appends 33 circles to the <g> element, with the cy attribute between y(1) and y(33).

Now, the question that follows is that, you specified a domain with a minimum of 5 with y.domain([5, 33]). Yet the data-bounded array, generated with d3.range, always begins with 1 and increments up to the value of THC. So some of the values in the array (1,2,3, and 4) always fall outside the y-axis, but d3 was able to translate it to a proper y-position. Is that possible? By default, yes, d3.scale extrapolates when the data is outside of the domain.

By default, clamping is disabled, such that if a value outside the input domain is passed to the scale, the scale may return a value outside the output range through linear extrapolation. For example, with the default domain and range of [0,1], an input value of 2 will return an output value of 2.

Related