Home > Back-end >  Path and graph elements not displaying in D3
Path and graph elements not displaying in D3

Time:10-13

I would like to plot a series of lines based on my dataset. It's hard to figure out what's wrong because I'm not receiving any error messages. I'm hoping someone more experienced than me is familiar with some of the common pitfalls.

This is an adaptation of two examples: structuring d3 code with es6 classes, and d3 multiline chart

Here are the relevant parts (where I think things are going wrong):

  createScales(){
    this.keynames = d3.scaleOrdinal();

    this.keynames.domain(Object.keys(this.data[0]).filter(key => key!=='date'));

    this.keymap = this.keynames.domain().map(
      keyname => ({name: keyname, values: this.data.map(
        d => ({date: d.date, key:  d[keyname]})
      )})
    );

    const m = this.margin;

    const xExtent = d3.extent(this.keymap, d => d.date);

    const yExtent = [0,d3.max(this.keymap, d => Math.max(d.values, function(v){ return v.key }) )];

    this.xScale = d3.scaleTime()
                    .range([0, this.width-m.right])
                    .domain(xExtent).nice();

    this.yScale = d3.scaleLinear()
                    .range([this.height-(m.top m.bottom), 0])
                    .domain(yExtent).nice();
  }

  addAxes(){
    const m = this.margin;

    const xAxis = d3.axisBottom()
                    .scale(this.xScale)
                    .ticks(8);

    const yAxis = d3.axisLeft()
                    .scale(this.yScale)
                    .ticks(4);

    this.plot.append("g")
             .attr("class", "x axis")
             .attr("transform", `translate(0, ${this.height-(m.top m.bottom)})`)
             .call(xAxis.ticks(8)); //more than 8 ticks shown

    this.plot.append("g")
             .attr("class", "y axis")
             .call(yAxis.ticks(4)) //more than 4 ticks shown, rest of chain not working
             .append("text")
             .attr("transform", "rotate(-90)")
             .attr("y", 6)
             .attr("dy", ".71em") 
             .style("text-anchor", "end")
             .text("$USD");
  }

  addLine(){

    const line = d3.line()
                   .x(function(d){console.log(d); return this.xScale(d.date)}) //no log, no line
                   .y(function(d){return this.yScale(d.key)});

    this.plot.append('path')
             .datum(this.keymap)
             .classed('line', true)
             .attr('d', function(d){return line(d.values)}) //logs correct values, no line(data structure outlined below(1))
             .style('stroke', this.lineColor || 'red')
             .style('fill', 'none');

  }

(1) A sample of the data as shown in this console log:

Array(4):
     0: {name: aapl, values: Array(691)}
          values: [0 .. 99]:
                  0: {date: Monday etc.., key: 38}
                  1: {date: Tuesday etc.., key: 39}
                  2: {date: Wednesday etc.., key: 38} ... etc

     1: {name: tsla, values: Array(691)}
     etc.

I don't see where the problem is.. my data structure is the exact same as the one in the example, and my previous class-less implementation of it works great.. The axis elements not showing up are just the cherry on the cake but perhaps they point to a greater issue. Thank you.

Complete code and data: file 1: plot.js

const chart = new Chart({element: document.querySelector('#graph')});

let getData = d3.csv('d1.csv', function(d){
                        function removeNaN(e,c){
                          if (e>0) {return e} else {return c}
                        }
                        return { date: d3.timeParse("%Y-%m-%d")(d.Date),
                                 aapl : d.AAPL, tsla : d.TSLA,
                                 aapl_sma: removeNaN( d.SMA_AAPL,d.AAPL),
                                 tsla_sma: removeNaN( d.SMA_TSLA,d.TSLA)
                        }
                      }).then(init);

function init(data){
  chart.setData(data);
}

File 2: chart.js

class Chart{
  constructor(opts){
    this.data = opts.data;
    this.element = opts.element;

  }

  draw(){
    this.width = this.element.offsetWidth;
    this.height = this.width/2;
    this.padding = 50;
    this.margin = {
      top : 20,
      bottom : 20,
      left : 30,
      right : 50
    };

    this.element.innerHTML = '';
    const svg = d3.select(this.element).append('svg');
    svg.attr('width', this.width);
    svg.attr('height', this.height);

    this.plot = svg.append('g')
                   .attr('transform', `translate(${this.margin.left},${this.margin.top})`);

    this.createScales();
    this.addAxes();
    this.addLine();

  }

  createScales(){
    this.keynames = d3.scaleOrdinal();

    this.keynames.domain(Object.keys(this.data[0]).filter(key => key!=='date'));

    this.keymap = this.keynames.domain().map(
      keyname => ({name: keyname, values: this.data.map(
        d => ({date: d.date, key:  d[keyname]})
      )})
    );

    const m = this.margin;

    const xExtent = d3.extent(this.data, d => d.date);

    const yExtent = [0,d3.max(this.keymap, d => d3.max(d.values, function(v){ return v.key }) )];

    this.xScale = d3.scaleTime()
                    .range([0, this.width-m.right])
                    .domain(xExtent).nice();

    this.yScale = d3.scaleLinear()
                    .range([this.height-(m.top m.bottom), 0])
                    .domain(yExtent).nice();

  }

  addAxes(){
    const m = this.margin;

    const xAxis = d3.axisBottom()
                    .scale(this.xScale);

    const yAxis = d3.axisLeft()
                    .scale(this.yScale);

    this.plot.append("g")
             .attr("class", "x axis")
             .attr("transform", `translate(0, ${this.height-(m.top m.bottom)})`)
             .call(xAxis.ticks(8));

    this.plot.append("g")
             .attr("class", "y axis")
             .call(yAxis.ticks(4))
             .append("text")
             .attr("transform", "rotate(-90)")
             .attr("y", 6)
             .attr("dy", ".71em")
             .style("text-anchor", "end")
             .text("$USD");

  }

  addLine(){
    const line = d3.line()
                   .x(function(d){console.log(d); return this.xScale(d.date)})
                   .y(function(d){return this.yScale(d.key)});

    this.plot.append('path')
             .datum(this.keymap)
             .classed('line', true)
             .attr('d', function(d){console.log(d); return line(d.values)})
             .style('stroke', this.lineColor || 'red')
             .style('fill', 'none');
    console.log(this.plot)
  }

  setColor(newColor){
    this.plot.select('.line')
             .style('stroke', newColor);

    this.lineColor = newColor;

  }

  setData(data){
    this.data = data;

    this.draw();

  }
}

File 3: index.html

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>Graphs of the US Economy</title>
    <link href="https://fonts.googleapis.com/css2?family=Source Sans Pro:wght@200;400;900&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
    <style>
    </style>
  </head>
  <body>
    <div id="container" class="container">
      <div id='graph'></div>
      <div id="sections">
        <div><h1>THIS IS A TEST SECTION THAT I'M USING TO FIGURE OUT HOW MY CODE WORKS</h1></div>
        <div><h1>THIS IS A SECOND TEST SECTION THAT I'M USING TO FIGURE OUT HOW MY CODE WORKS</h1></div>
        <div><h1>THIS IS A THIRD TEST SECTION THAT I'M USING TO FIGURE OUT HOW MY CODE WORKS</h1></div>
        <div><h1>THIS IS A FOURTH TEST SECTION THAT I'M USING TO FIGURE OUT HOW MY CODE WORKS</h1></div>
        <div><h1>THIS IS A FIFTH TEST SECTION THAT I'M USING TO FIGURE OUT HOW MY CODE WORKS</h1></div>
        <div><h1>THIS IS A SIXTH TEST SECTION THAT I'M USING TO FIGURE OUT HOW MY CODE WORKS</h1></div>
      </div>
    </div>
    
    <script src="https://d3js.org/d3.v6.js"></script>
    <script src="graph-scroll.js"></script>
    <script src="chart.js"></script>
    <script src="plot.js"></script>
    <script>
    </script>
  </body>
</html>

file 4: d1.csv, 39 rows:

Date,AAPL,SMA_AAPL,TSLA,SMA_TSLA
2018-12-31,38.33848571777344,,66.55999755859375,
2019-01-02,38.382225036621094,,62.02399826049805,
2019-01-03,34.55907440185547,,60.071998596191406,
2019-01-04,36.03437805175781,,63.53799819946289,
2019-01-07,35.95417022705078,,66.99199676513672,
2019-01-08,36.63956832885742,,67.06999969482422,
2019-01-09,37.26177215576172,,67.70600128173828,
2019-01-10,37.380863189697266,,68.99400329589844,
2019-01-11,37.013858795166016,,69.4520034790039,
2019-01-14,36.4572868347168,,66.87999725341797,
2019-01-15,37.20343780517578,,68.88600158691406,
2019-01-16,37.657936096191406,,69.20999908447266,
2019-01-17,37.88154602050781,,69.46199798583984,
2019-01-18,38.11487579345703,,60.45199966430664,
2019-01-22,37.259342193603516,,59.784000396728516,
2019-01-23,37.410030364990234,,57.518001556396484,
2019-01-24,37.113521575927734,,58.301998138427734,
2019-01-25,38.34333801269531,,59.40800094604492,
2019-01-28,37.988487243652344,,59.2760009765625,
2019-01-29,37.59474182128906,,59.492000579833984,
2019-01-30,40.16377258300781,,61.75400161743164,
2019-01-31,40.453006744384766,,61.40399932861328,
2019-02-01,40.472450256347656,,62.44200134277344,
2019-02-04,41.622066497802734,,62.577999114990234,
2019-02-05,42.33420181274414,,64.2699966430664,
2019-02-06,42.34878158569336,,63.444000244140625,
2019-02-07,41.546722412109375,,61.50199890136719,
2019-02-08,41.59553909301758,,61.15999984741211,
2019-02-11,41.35633087158203,,62.56800079345703,
2019-02-12,41.71269989013672,38.606483713785806,62.36199951171875,63.48539975484212
2019-02-13,41.539398193359375,38.71318079630534,61.63399887084961,63.32119979858398
2019-02-14,41.69073486328125,38.823464457194014,60.75400161743164,63.278866577148435
2019-02-15,41.59797286987305,39.05809440612793,61.57600021362305,63.32899996439616
2019-02-19,41.72246551513672,39.247697321573895,61.12799835205078,63.24866663614909
2019-02-20,41.990962982177734,39.44892374674479,60.512001037597656,63.032666778564455
2019-02-21,41.75419616699219,39.619411341349284,58.24599838256836,62.738533401489256
2019-02-22,42.22041702270508,39.78469950358073,58.94200134277344,62.44640007019043
2019-02-25,42.5279655456543,39.95626958211263,59.75400161743164,62.13840001424153

CodePudding user response:

The main problem is in the addLine method. First, try adding a console.log(this) to the function that you pass to .x():

const line = d3.line()
    .x(function(d){console.log('this:', this); return this.xScale(d.date)})
    .y(function(d){return this.yScale(d.key)});

You'll see that in this function, this is undefined, so you can't access this.xScale. This is due to the behavior of this inside of functions.

Consider this example:

class Example {
      constructor(x) {
        this.x = x;
      }

      print() {
        console.log('in print method:', this);
        
        function normal() {
          console.log('in normal function:', this);
        }

        const arrow = () => console.log('in arrow function:', this);

        normal();
        arrow();
      }
  }

  const ex = new Example(4);
  ex.print();

this is undefined in a normal function, but in an arrow function, this refers to the object. So, we can adjust the line generator to use arrow functions:

const line = d3.line()
    .x(d => this.xScale(d.date))
    .y(d => this.yScale(d.key));

Second, you want to draw one line for each element in this.keymap, so you'll need to do a data join:

this.plot.append('g')
  .selectAll('path')
  .data(this.keymap)
  .join('path')
    .classed('line', true)
    .attr('d', function (d) { return line(d.values) })
    .style('stroke', this.lineColor || 'red')
    .style('fill', 'none');

Here's a complete example. I've changed how the data is passed into the chart so that I can put it all in a snippet.

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <script src="https://d3js.org/d3.v7.js"></script>
</head>

<body>
  <div id="graph"></div>

  <script>
    class Chart {
      constructor(opts) {
        this.data = opts.data;
        this.element = opts.element;
      }

      draw() {
        this.width = this.element.offsetWidth;
        this.height = this.width / 2;
        this.padding = 50;
        this.margin = {
          top: 20,
          bottom: 20,
          left: 30,
          right: 50
        };

        this.element.innerHTML = '';
        const svg = d3.select(this.element).append('svg');
        svg.attr('width', this.width);
        svg.attr('height', this.height);

        this.plot = svg.append('g')
            .attr('transform', `translate(${this.margin.left},${this.margin.top})`);

        this.createScales();
        this.addAxes();
        this.addLine();
      }

      createScales() {
        this.keynames = d3.scaleOrdinal();

        this.keynames.domain(Object.keys(this.data[0]).filter(key => key !== 'date'));

        this.keymap = this.keynames.domain().map(
          keyname => ({
            name: keyname, values: this.data.map(
              d => ({ date: d.date, key:  d[keyname] })
            )
          })
        );

        const m = this.margin;

        const xExtent = d3.extent(this.data, d => d.date);

        const yExtent = [0, d3.max(this.keymap, d => d3.max(d.values, function (v) { return v.key }))];

        this.xScale = d3.scaleTime()
            .range([0, this.width - m.right])
            .domain(xExtent).nice();

        this.yScale = d3.scaleLinear()
            .range([this.height - (m.top   m.bottom), 0])
            .domain(yExtent).nice();
      }

      addAxes() {
        const m = this.margin;

        const xAxis = d3.axisBottom()
          .scale(this.xScale);

        const yAxis = d3.axisLeft()
          .scale(this.yScale);

        this.plot.append("g")
            .attr("class", "x axis")
            .attr("transform", `translate(0, ${this.height - (m.top   m.bottom)})`)
            .call(xAxis.ticks(8));

        this.plot.append("g")
            .attr("class", "y axis")
            .call(yAxis.ticks(4))
          .append("text")
            .attr("transform", "rotate(-90)")
            .attr("y", 6)
            .attr("dy", ".71em")
            .attr("fill", "black")
            .style("text-anchor", "end")
            .text("$USD");
      }

      addLine() {
        const line = d3.line()
          .x(d => this.xScale(d.date))
          .y(d => this.yScale(d.key));

        this.plot.append('g')
          .selectAll('path')
          .data(this.keymap)
          .join('path')
            .classed('line', true)
            .attr('d', function (d) { return line(d.values) })
            .style('stroke', this.lineColor || 'red')
            .style('fill', 'none');
      }

      setColor(newColor) {
        this.plot.select('.line')
          .style('stroke', newColor);

        this.lineColor = newColor;
      }

      setData(data) {
        this.data = data;

        this.draw();
      }
    }

    const chart = new Chart({ element: document.querySelector('#graph') });

    const data = d3.csvParse(`Date,AAPL,SMA_AAPL,TSLA,SMA_TSLA
2018-12-31,38.33848571777344,,66.55999755859375,
2019-01-02,38.382225036621094,,62.02399826049805,
2019-01-03,34.55907440185547,,60.071998596191406,
2019-01-04,36.03437805175781,,63.53799819946289,
2019-01-07,35.95417022705078,,66.99199676513672,
2019-01-08,36.63956832885742,,67.06999969482422,
2019-01-09,37.26177215576172,,67.70600128173828,
2019-01-10,37.380863189697266,,68.99400329589844,
2019-01-11,37.013858795166016,,69.4520034790039,
2019-01-14,36.4572868347168,,66.87999725341797,
2019-01-15,37.20343780517578,,68.88600158691406,
2019-01-16,37.657936096191406,,69.20999908447266,
2019-01-17,37.88154602050781,,69.46199798583984,
2019-01-18,38.11487579345703,,60.45199966430664,
2019-01-22,37.259342193603516,,59.784000396728516,
2019-01-23,37.410030364990234,,57.518001556396484,
2019-01-24,37.113521575927734,,58.301998138427734,
2019-01-25,38.34333801269531,,59.40800094604492,
2019-01-28,37.988487243652344,,59.2760009765625,
2019-01-29,37.59474182128906,,59.492000579833984,
2019-01-30,40.16377258300781,,61.75400161743164,
2019-01-31,40.453006744384766,,61.40399932861328,
2019-02-01,40.472450256347656,,62.44200134277344,
2019-02-04,41.622066497802734,,62.577999114990234,
2019-02-05,42.33420181274414,,64.2699966430664,
2019-02-06,42.34878158569336,,63.444000244140625,
2019-02-07,41.546722412109375,,61.50199890136719,
2019-02-08,41.59553909301758,,61.15999984741211,
2019-02-11,41.35633087158203,,62.56800079345703,
2019-02-12,41.71269989013672,38.606483713785806,62.36199951171875,63.48539975484212
2019-02-13,41.539398193359375,38.71318079630534,61.63399887084961,63.32119979858398
2019-02-14,41.69073486328125,38.823464457194014,60.75400161743164,63.278866577148435
2019-02-15,41.59797286987305,39.05809440612793,61.57600021362305,63.32899996439616
2019-02-19,41.72246551513672,39.247697321573895,61.12799835205078,63.24866663614909
2019-02-20,41.990962982177734,39.44892374674479,60.512001037597656,63.032666778564455
2019-02-21,41.75419616699219,39.619411341349284,58.24599838256836,62.738533401489256
2019-02-22,42.22041702270508,39.78469950358073,58.94200134277344,62.44640007019043
2019-02-25,42.5279655456543,39.95626958211263,59.75400161743164,62.13840001424153`, function (d) {
      function removeNaN(e, c) {
        if (e > 0) { return e; } else { return c; }
      }
      return {
        date: d3.timeParse("%Y-%m-%d")(d.Date),
        aapl:  d.AAPL,
        tsla:  d.TSLA,
        aapl_sma: removeNaN( d.SMA_AAPL,  d.AAPL),
        tsla_sma: removeNaN( d.SMA_TSLA,  d.TSLA)
      };
    });

    chart.setData(data);
  </script>
</body>

</html>

  • Related