1

I am trying to draw a scatter plot chart with D3 from a csv file. The csv looks something like this:

location    smokeSomeDays   allFamPovPct
AL                5%             15%
AK                5%             8%
AZ                4%             13%
AR                6%             14%
CA                4%             12%
CO                4%             8%
CT                4%             8%
DE                4%             9%
DC                5%             14%
...
...
...

I want the Y-Axis to be 'smokeSomeDays' and the X-Axis to be 'allFamPovPct'

Here is my js that evokes the "Cannot read property 'getAttribute' of null" error, even though 'getAttribute' is not mentioned anywhere in the code:

var svgW = 960;
var svgH = 500;

var margin = { top: 20, right: 40, bottom: 60, left: 50 };

var chartWidth = svgW - margin.left - margin.right;
var chartHeight = svgH - margin.top - margin.bottom;

var svg = d3.select("body")
  .append("svg")
  .attr("width", svgW)
  .attr("height", svgH)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

d3.csv('data.csv', function(data) {
  data.forEach(function () {
  // Scales
    var xScale = d3.scaleLinear()
    .domain([
      d3.min([0,d3.min(data,function (d) { return d.allFamPovPct })]),
      d3.max([0,d3.max(data,function (d) { return d.allFamPovPct })])
      ])
    .range([0,chartWidth])
  var yScale = d3.scaleLinear()
    .domain([
      d3.min([0,d3.min(data,function (d) { return d.smokeSomeDays })]),
      d3.max([0,d3.max(data,function (d) { return d.smokeSomeDays })])
      ])
    .range([chartHeight,0])
  // X-axis
  var xAxis = d3.axisBottom()
    .scale(xScale)
    .ticks(5)
  // Y-axis
  var yAxis = d3.axisLeft()
    .scale(yScale)
    .ticks(5)
  // Circles
  var circles = svg.selectAll('circle')
      .data(data)
      .enter()
     .append('circle')
      .attr('cx',function (d) { return xScale(d.allFamPovPct) })
      .attr('cy',function (d) { return yScale(d.smokeSomeDays) })
      .attr('r','10')
      .attr('stroke','black')
      .attr('stroke-width',1)
      .attr('fill')

  svg.append('g')
      .attr('class','axis')
      .attr('transform', 'translate(0,' + chartHeight + ')')
      .call(xAxis)
    .append('text') // X-axis Label
      .attr('class','label')
      .attr('y',-10)
      .attr('x',chartWidth)

  svg.append('g')
      .attr('class', 'axis')
      .call(yAxis)
     .append('text') // y-axis Label
      .attr('class','label')
      .attr('transform','rotate(-90)')
      .attr('x',0)
      .attr('y',5)

});
});

The full error msg looks like this:

Uncaught TypeError: Cannot read property 'getAttribute' of null  d3.min.js:4
at dt.ul [as attr] (d3.min.js:4)
at app.js:49
at Array.forEach (<anonymous>)
at app.js:17
at d3.min.js:3
at Object.<anonymous> (d3.min.js:7)
at v.call (d3.min.js:4)
at XMLHttpRequest.e (d3.min.js:7)

But all the lines mentioned in the error looks good to me, maybe I'm missing something.

The code also doesn't produce a scatter plot that I want. It gives me something like this:

|
|
|
|
|
|
|
|
|
O ---------------------------------------

Here is my html, if it helps:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Data Journalism</title>
<style>
.chart {

}

.main text {
    font: 10px sans-serif;  
}

.axis circle, .axis path {
    shape-rendering: crispEdges;
    stroke: black;
    fill: none;
}

circle {
    fill: steelblue;
}

</style>
        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.7.3/d3.min.js">
</script>
      </head>
      <body>
        <div class='content'></div>
        <script type="text/javascript" src="app.js"></script>
      </body>
    </html>

Thanks for your help in advance!

Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
song
  • 83
  • 2
  • 7

2 Answers2

2

The error you get right now is due to the fact that...

.attr("fill")

... is a getter, not a setter. Have in mind that this error is thrown by D3 library, but your console shows the line of your code that is the actual culprit.

However, this is the least of your problems here.

The first big problem is that forEach inside the callback. That is completely unnecessary in a D3 code. Get rid of it.

The second problem is that smokeSomeDays and allFamPovPct are strings, not numbers. You have to convert them to numbers to use your scale. Here, in this example, I'm using a row function:

function(d){
    d.smokeSomeDays = +d.smokeSomeDays.split("%")[0];
    d.allFamPovPct = +d.allFamPovPct.split("%")[0];
    return d;
}

Here is your code with those changes (and just a couple of rows of your CSV):

var csv = `location,smokeSomeDays,allFamPovPct
AL,5%,15%
AK,5%,8%
AZ,4%,13%
AR,6%,14%`;

var svgW = 400;
var svgH = 300;

var margin = {
  top: 20,
  right: 40,
  bottom: 60,
  left: 50
};

var chartWidth = svgW - margin.left - margin.right;
var chartHeight = svgH - margin.top - margin.bottom;

var svg = d3.select("body")
  .append("svg")
  .attr("width", svgW)
  .attr("height", svgH)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var data = d3.csvParse(csv, function(d) {
  d.smokeSomeDays = +d.smokeSomeDays.split("%")[0];
  d.allFamPovPct = +d.allFamPovPct.split("%")[0];
  return d;
})

var xScale = d3.scaleLinear()
  .domain([
    d3.min([0, d3.min(data, function(d) {
      return d.allFamPovPct
    })]),
    d3.max([0, d3.max(data, function(d) {
      return d.allFamPovPct
    })])
  ])
  .range([0, chartWidth])
var yScale = d3.scaleLinear()
  .domain([
    d3.min([0, d3.min(data, function(d) {
      return d.smokeSomeDays
    })]),
    d3.max([0, d3.max(data, function(d) {
      return d.smokeSomeDays
    })])
  ])
  .range([chartHeight, 0])
  // X-axis
var xAxis = d3.axisBottom()
  .scale(xScale)
  .ticks(5)
  // Y-axis
var yAxis = d3.axisLeft()
  .scale(yScale)
  .ticks(5)
  // Circles
var circles = svg.selectAll('circle')
  .data(data)
  .enter()
  .append('circle')
  .attr('cx', function(d) {
    return xScale(d.allFamPovPct)
  })
  .attr('cy', function(d) {
    return yScale(d.smokeSomeDays)
  })
  .attr('r', '10')
  .attr('stroke', 'black')
  .attr('stroke-width', 1)
  .attr("fill", "teal");

svg.append('g')
  .attr('class', 'axis')
  .attr('transform', 'translate(0,' + chartHeight + ')')
  .call(xAxis)
  .append('text') // X-axis Label
  .attr('class', 'label')
  .attr('y', -10)
  .attr('x', chartWidth)

svg.append('g')
  .attr('class', 'axis')
  .call(yAxis)
  .append('text') // y-axis Label
  .attr('class', 'label')
  .attr('transform', 'rotate(-90)')
  .attr('x', 0)
  .attr('y', 5)
<script src="https://d3js.org/d3.v4.min.js"></script>
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
0

It might be issue with loading library multiple times/initialising same id multiple times

K.B
  • 885
  • 5
  • 10