Search code examples
javascriptd3.js

D3: line chart not rendering properly (but bar plot on same data is rendering)


I have the following data in a csv file named salesData.csv:

    let salesData = [
        {
            year : 2021,
            sales : 171,
            expenses : 64
        },
    {
        year : 2020,
        sales : 145,
        expenses : 64
    },

    {
        year : 2019,
        sales : 147,
        expenses : 64
    },

    { 
        year : 2018,
        sales : 161,
        expenses : 64
    },

    { 
        year : 2017,
        sales : 171,
        expenses : 64
    },

    { 
        year : 2016,
        sales : 141,
        expenses : 52
    },

    { 
        year : 2015,
        sales : 115,
        expenses : 52
    },

    { 
        year : 2014,
        sales : 132,
        expenses : 52
    },

    { 
        year : 2013,
        sales : 146,
        expenses : 52
    }
]

My goal is to render a line chart based on user selection of a "metric" (in this case, either sales or expenses).

I can render a bar chart with this data, but not a line chart based on the same data. Here's what it looks like:

enter image description here

Why does the line chart not render properly?

Thanks in advance for your help!

Here is my code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Sales Data</title>
  <style type="text/css">

    .line {
      fill: none;
      stroke: teal;
      stroke-width: 2;
    }

  </style>
</head>
<body>

<div id="chart-area"></div>

<script type="text/javascript">

  let margin = {top: 40, right: 40, bottom: 60, left: 60};

  let width = 600 - margin.left - margin.right;
  let height = 500 - margin.top - margin.bottom;

  let svg = d3.select("#chart-area").append("svg")
          .attr('viewBox', [0, 0, width + margin.left + margin.right, height + margin.top + margin.bottom] )
          .append("g")
          .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  let padding = 30;

  // Date parser
  let parseDate = d3.timeParse("%Y");

  // Creates scale functions
  let xScale = d3.scaleTime()
          .range([ 0, width ]);

  let yScale = d3.scaleLinear()
          .range([ height, 0 ]);

  // Defines axes
  let xAxis = d3.axisBottom()
          .scale(xScale);

  let yAxis = d3.axisLeft()
          .scale(yScale);

  let xAxisGroup = svg.append('g')
          .attr('class', 'axis')
          .attr('transform', 'translate(0,' + height + ')')
          .call(xAxis);

  let yAxisGroup = svg.append('g')
          .attr('class', 'axis')
          .attr('transform', 'translate(0, 0)')
          .call(yAxis);

  let xAxisLabel = svg.append('text')
          .attr('id', 'y-axis-label')
          .attr('class', 'axis-label');


  // Initialize data
  loadSalesData();

  // Data
  let data;


  // Load CSV file
  function loadSalesData() {
    d3.csv("salesData.csv", row => {
      row.YEAR = parseDate(row.YEAR);
      row.REVENUE = +row.REVENUE;
      row.EXPENSES = +row.EXPENSES;
      return row
    }).then(csv => {

      // Store csv data in global variable
      data = csv;

      // Draw the chart
      renderChart();
    });
  }

  // Renders the chart
  function renderChart() {

    console.table(data);

    // Gets the user's selection from the drop-down menu
    let metricType = d3.select('#metric-type').property('value');
    console.log(metricType);

    // Sorts the data based on the user's selected metric
    data.sort(( a, b ) => b[metricType] - a[metricType]);

    // Specifies the DOMAINS
    xScale.domain([d3.min(data, function(d) {return d.year; }), d3.max(data, function(d) {return d.year; }) ]);
    yScale.domain([0, d3.max(data, function(d) {return d[metricType]; }) ]);

    let lineGenerator = d3.line()
            .x(function (d) { return xScale(d.year); })
            .y(function (d) { return yScale(d[metricType]); })
            .curve(d3.curveMonotoneX)(data)
    ;


    // Draws bar chart
    let bars = svg.selectAll('.bar')
            .data(data);

    bars
            .exit()
            .remove()

    bars
            .enter()
            .append('rect')
            .attr('class', 'bar')

            .merge(bars)

            .transition()
            .duration(800)

            .attr('x', d => xScale(d.YEAR))
            .attr('y', d => yScale(d[metricType]))
            .attr('height', d => (height - yScale(d[metricType])))
            .attr('width', 10);

    // Draws line chart
    let linePlot = svg.append('path');

    linePlot
            .datum(data)
            .attr('class', 'line')
            .attr('d', lineGenerator)


    xAxisGroup
            .transition()
            .duration(1000)
            .style('font-size', '15px')
            .call(xAxis);

    yAxisGroup
            .transition()
            .duration(1000)
            .style('font-size', '15px')
            .call(yAxis);

  }

</script>

<script src="https://d3js.org/d3.v7.min.js"></script>

</body>
</html>

Solution

  • The issue is with the sort.

    This:

    data.sort(( a, b ) => b[metricType] - a[metricType]);
    

    Needs to become this:

    data.sort(( a, b ) => b[metricType] - a[metricType]);
    data.sort(( a, b ) => b.YEAR - a.YEAR);