Will Luce
Will Luce

Reputation: 1841

Summing elements on an array of d3 objects

I'm trying to return the averaged value of the gradient element in a d3 area element. If I just run console.log(dataGroup[i].gradient) in the loop, it logs the gradients just fine. When I try to += though, it's telling me cannot read property gradient of undefined.

I've pulled out the function here and pasted the full code below. The problem call and function have been labeled in the full code.

// PROBLEM FUNCTION
function dataGroupGradient(dataGroup)
{
  var sum = dataGroup[0].gradient;
  for (var i = 1; i <= dataGroup.length; i++)
  {
    sum += parseFloat(dataGroup[i].gradient);  
  }
  return sum/dataGroup.length;
} 

Here's a Plunk

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
    font: 12px Arial;
}

text.shadow {
  stroke: #fff;
  stroke-width: 2.5px;
  opacity: 0.9;
}

path { 
    stroke: steelblue;
    stroke-width: 2;
    fill: none;
}

.axis path,
.axis line {
    fill: none;
    stroke: grey;
    stroke-width: 1;
    shape-rendering: crispEdges;
}

.grid .tick {
    stroke: lightgrey;
    stroke-opacity: 0.7;
    shape-rendering: crispEdges;
}
.grid path {
          stroke-width: 0;
}

.area {
    stroke-width: 0;
}

</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>

<script>

var margin = {top: 30, right: 20, bottom: 35, left: 50},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;

var color = d3.scale.ordinal()
    .domain([-10,-5,0,5,10])
    .range(['#a1d99b','#c7e9c0','#fdd0a2','#fdae6b','#fd8d3c','#e6550d']);

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

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

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

var area = d3.svg.area()
    .x(function(d) { return x(d.distance); })
    .y0(height)
    .y1(function(d) { return y(d.elevation); });

var valueline = d3.svg.line()
    .x(function(d) { return x(d.distance); })
    .y(function(d) { return y(d.elevation); });

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

// function for the x grid lines
function make_x_axis() {
    return d3.svg.axis()
        .scale(x)
        .orient("bottom")
        .ticks(5)
}

// function for the y grid lines
function make_y_axis() {
  return d3.svg.axis()
      .scale(y)
      .orient("left")
      .ticks(5)
}

// PROBLEM FUNCTION
function dataGroupGradient(dataGroup)
{
  var sum = dataGroup[0].gradient;
  for (var i = 1; i <= dataGroup.length; i++)
  {
    sum += parseFloat(dataGroup[i].gradient);  
  }
  return sum/dataGroup.length;
}

// Get the data
d3.csv("data.csv", function(error, data) {
    data.forEach(function(d) {
        d.distance = +d.distance;
        d.elevation = +d.elevation;
        d.gradient = +d.gradient;
    });

    var dataGroup = d3.nest()
        .key(function(d) {
            return d.group;
        })
        .entries(data);

    dataGroup.forEach(function(group, i) {
      if(i < dataGroup.length - 1) {
        group.values.push(dataGroup[i+1].values[0])
      }
    })

    // Scale the range of the data
    x.domain(d3.extent(data, function(d) { return d.distance; }));
    y.domain([0, d3.max(data, function(d) { return d.elevation; })]);

    dataGroup.forEach(function(d, i){
        svg.append("path")
            .datum(d.values)
            .attr("class", "area")
            .attr("d", area);
        });

    // PROBLEM CALL
    svg.selectAll(".area")
        .style("fill", function(d) { return color(dataGroupGradient(d)); });

    // Add the valueline path.
    svg.append("path")
        .attr("d", valueline(data));

    // Add the X Axis
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    // Add the Y Axis
    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

    // Add the text label for the X axis
    svg.append("text")
        .attr("transform",
              "translate(" + (width/2) + " ," + 
                             (height+margin.bottom) + ")")
        .style("text-anchor", "middle")
        .text("Distance");

    // Add the text label for the Y axis
    svg.append("text")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("x", margin.top - (height / 2))
        .attr("dy", ".71em")
        .style("text-anchor", "end")
        .text("");

    // Add the title
    svg.append("text")
        .attr("x", (width / 2))     
        .attr("y", 0 - (margin.top / 2))
        .attr("text-anchor", "middle")
        .style("font-size", "16px")
        .style("text-decoration", "underline")
        .text("Elevation Graph");

});

</script>
</body>

Upvotes: 1

Views: 830

Answers (2)

altocumulus
altocumulus

Reputation: 21578

You could as well utilize the pure d3 way which is shorter and saves you from having to deal with accessing the array yourself:

function dataGroupGradient(dataGroup) {
    var sum = d3.sum(dataGroup, function(value) {
        return parseFloat(value.gradient);
    });

  return sum/dataGroup.length;
}

There may be an even briefer approach since you seem to be calculating the mean of the gradient properties:

function dataGroupGradient(dataGroup) {
    return d3.mean(dataGroup, function(value) {
        return parseFloat(value.gradient);
    });
}

Upvotes: 3

GregL
GregL

Reputation: 38103

This is just the ol' <= instead of < problem.

For any array, arr[arr.length] is undefined. (arr[arr.length - 1] is the last element).

Change your function to use < like so:

function dataGroupGradient(dataGroup)
{
  var sum = dataGroup[0].gradient;
  for (var i = 1; i < dataGroup.length; i++)
  {
    sum += parseFloat(dataGroup[i].gradient);  
  }
  return sum/dataGroup.length;
} 

Upvotes: 0

Related Questions