Search code examples
javascriptd3.jsscale

d3.js linear scale returns NaN


I'm trying to build my first visualization with d3.js and I have som issue scaling data from a JSON source (myData). This is how I'm scaling it and creating svg circles:

var myData = [{
  "x": "45.4969717",
  "y": "10.605375399999957"
}, {
  "x": "45.4847328802866",
  "y": "9.236798286437988"
}, {
  "x": "45.4838657",
  "y": "9.25125330000003"
}]

var xline = d3.scaleLinear().domain(45.103300, 40.1053737).range([0, 450.57913]);
var yline = d3.scaleLinear().domain(12.186311, 12.605783).range([0, 350]);
var svgViewport = d3.select("#mappa-italia").append("g").attr("id", "locations");
var circleElements = svgViewport.selectAll("circle").data(myData).enter().append("circle").attr("cx", function(d) {
  return xline(d.x);
}).attr("cy", function(d) {
  return yline(d.y);
}).attr("r", "5");
<script src="https://d3js.org/d3.v5.min.js"></script>

<svg id="mappa-italia"></svg>

The error returned by the console is:

Error: <circle> attribute cx: Expected length, "NaN".

Error: <circle> attribute cy: Expected length, "NaN".

I'm using d3.js version 5


Solution

  • It's not advised to set a domain using static values. Here's a similar example for what you're looking for:

    https://bl.ocks.org/mbostock/3887118

    Using the following changes:

    1. Defining width, height, margins as variables.

      var margin = {top: 10, bottom: 10, left: 10, right: 10},
        width = 600-margin.left-margin.right,
        height = 400-margin.top-margin.bottom;
      
    2. Reason for the linear scale returning NaN is becuase the data values (x & y) are strings and not numbers. To parse the data, here's one approach:

      // parse data
      myData.forEach(function(d) {
       d.x = +d.x;
       d.y = +d.y;
      });
      
    3. Important advise: Avoid using static values while setting the axes domains. Make use of d3 array manipulations-min,max,extent. I'm using d3-extent

      var xline = d3.scaleLinear().domain(d3.extent(myData, function(d) { return d.x; })).nice().range([0, width]);
      var yline = d3.scaleLinear().domain(d3.extent(myData, function(d) { return d.y; })).nice().range([height, 0]);
      
    4. As the data is now parsed (2nd step), linear scale wouldn't have issue plotting the values.

      var circleElements = svgViewport.selectAll("circle").data(myData).enter().append("circle").attr("cx", function(d) {
        return xline(d.x);
      }).attr("cy", function(d) {
        return yline(d.y);
      })
      

    Just for aesthetics, I'm using a color scale to add colors to the circles:

    var colorScale = d3.scaleOrdinal(d3.schemeCategory10);
    

    Here's a code snippet:

      var myData = [{
        "x": "45.4969717",
        "y": "10.605375399999957"
      }, {
        "x": "45.4847328802866",
        "y": "9.236798286437988"
      }, {
        "x": "42.4838657",
        "y": "9.25125330000003",
      }, {
        "x": "40.1053737",
        "y": "12"
      }, {
      	"x": "42.4",
        "y": "10.4"
      }];
      
      var margin = {top: 10, bottom: 10, left: 10, right: 10},
     		  width = 450.57913-margin.left-margin.right,
      		height = 350-margin.top-margin.bottom;
      
      
    // parse data
    myData.forEach(function(d) {
    	d.x = +d.x;
      d.y = +d.y;
    });
    
      var xline = d3.scaleLinear().domain(d3.extent(myData, function(d) { return d.x; })).nice().range([0, width]);
      var yline = d3.scaleLinear().domain(d3.extent(myData, function(d) { return d.y; })).nice().range([height, 0]);
      
      var colorScale = d3.scaleOrdinal(d3.schemeCategory10);
      
      var svgViewport = d3.select("#mappa-italia")
      			.attr('width', width+margin.left+margin.right)
            .attr('height', height+margin.top+margin.bottom)
            .append("g")
            .attr('transform', 'translate('+margin.left+', ' + margin.top+')')
            .attr("id", "locations");
      var circleElements = svgViewport.selectAll("circle").data(myData).enter().append("circle").attr("cx", function(d) {
        return xline(d.x);
      }).attr("cy", function(d) {
        return yline(d.y);
      }).attr("r", "5").style('fill', function(d, i) {
    		return colorScale(i);
      });
    <script src="https://d3js.org/d3.v5.min.js"></script>
    
    
    <svg id="mappa-italia"></svg>

    Hope this helps. (Btw I've added a couple of extra rows in the data)