Alexander Mills
Alexander Mills

Reputation: 100020

D3 multi-line plot is displaying 2 lines not 3

Ultimately, I am just trying to plot Node.js memory usage over time, in a simple multi-line plot. Maybe there is a package that already does this, but I don't see one. There are 3 components to Node.js/V8 memory usage, heapUsed, heapTotal, and rss, I'd like to plot them over time.

To do a multi-line plot with D3, I am following the example here: https://code.tutsplus.com/tutorials/building-a-multi-line-chart-using-d3js--cms-22935

the full code from the example is here: https://github.com/jay3dec/MultiLineChart_D3/blob/master/index.html

Here is my code:

<!DOCTYPE html>
<html lang='en'>

<head>

  <link href='http://getbootstrap.com/dist/css/bootstrap.min.css' rel='stylesheet'>
  <link href='http://getbootstrap.com/examples/justified-nav/justified-nav.css' rel='stylesheet'>
  <script src='http://d3js.org/d3.v3.min.js' charset='utf-8'></script>

  <style>

    .axis path {
      fill: none;
      stroke: #777;
      shape-rendering: crispEdges;
    }

    .axis text {
      font-family: Lato;
      font-size: 13px;
    }

  </style>

</head>

<body>

<div class='container'>

  <div class='jumbotron'>

    <svg id='visualisation' width='1000' height='500'></svg>

    <script>

      var heapTotal = [
        {x: Date.now() - 500, y: 100},
        {x: Date.now(), y: 125},
        {x: Date.now() + 500, y: 150},
      ];

      var heapUsed = [
        {x: Date.now() - 500, y: 110},
        {x: Date.now(), y: 111},
        {x: Date.now() + 500, y: 112},
      ];

      var rss = [
        {x: Date.now() - 500, y: 44},
        {x: Date.now(), y: 44},
        {x: Date.now() + 500, y: 44},
      ];

      const values = heapTotal.concat(heapUsed).concat(rss).reduce(function (prev, curr) {

        console.log('curr => ', curr);

        return {
          xMin: Math.min(prev.xMin, curr.x),
          xMax: Math.max(prev.xMax, curr.x),
          yMin: Math.min(prev.yMin, curr.y),
          yMax: Math.max(prev.yMax, curr.y),
        }

      }, {
        xMin: Number.MAX_SAFE_INTEGER,
        xMax: -1,
        yMin: Number.MAX_SAFE_INTEGER,
        yMax: -1
      });

      console.log('values => ', values);

      var vis = d3.select('#visualisation'),
        WIDTH = 1200,
        HEIGHT = 800,
        MARGINS = {
          top: 20,
          right: 20,
          bottom: 20,
          left: 50
        },
        xScale = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([values.xMin, values.xMax]),
        yScale = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([values.yMin, values.yMax]),
        xAxis = d3.svg.axis()
        .scale(xScale),
        yAxis = d3.svg.axis()
        .scale(yScale)
        .orient('left');

      vis.append('svg:g')
      .attr('class', 'x axis')
      .attr('transform', 'translate(0,' + (HEIGHT - MARGINS.bottom) + ')')
      .call(xAxis);

      vis.append('svg:g')
      .attr('class', 'y axis')
      .attr('transform', 'translate(' + (MARGINS.left) + ',0)')
      .call(yAxis);

      var lineGen = d3.svg.line()
      .x(function (d) {
        return xScale(d.x);
      })
      .y(function (d) {
        return yScale(d.y);
      })
      .interpolate('basis');

      vis.append('svg:path')
      .attr('d', lineGen(heapUsed))
      .attr('stroke', 'green')
      .attr('stroke-width', 2)
      .attr('fill', 'none');

      vis.append('svg:path')
      .attr('d', lineGen(heapTotal))
      .attr('stroke', 'blue')
      .attr('stroke-width', 2)
      .attr('fill', 'none');

      vis.append('svg:path')
      .attr('d', lineGen(rss))
      .attr('stroke', 'red')
      .attr('stroke-width', 2)
      .attr('fill', 'none');


    </script>
  </div>

</div>

</body>

</html>

But for some reason, it only plots 2 lines, not 3:

enter image description here

Anybody have any idea why?

It appears that 3rd line just isn't visible given the dimensions - aka, the y axis does not go down to 44.

Here is screenshot of the values object from the code:

enter image description here

Upvotes: 2

Views: 174

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

The problem with your code is that your SVG selection (named vis) points to this SVG element...

<svg id='visualisation' width='1000' height='500'></svg>

... which has a height of 500px.

However, your vertical scale (yScale) uses the HEIGHT variable, which is 800.

Therefore, the axis is correct, but it's going beyond the lower limit of the SVG (you can clearly see that inspecting the element), as well as your third line, which is being painted, but it's not visible because it currently lies outside the SVG limits.

Solution: use the HEIGHT and WIDTH variables to set the height and width of your SVG:

vis.attr("width", WIDTH)
    .attr("height", HEIGHT)

Alternatively, if the height you want is in fact the inline height of that SVG (that is, 500), change your HEIGHT variable accordingly.

Here is your updated code, now you can see the last line:

var heapTotal = [{
   x: Date.now() - 500,
   y: 100
 }, {
   x: Date.now(),
   y: 125
 }, {
   x: Date.now() + 500,
   y: 150
 }, ];

 var heapUsed = [{
   x: Date.now() - 500,
   y: 110
 }, {
   x: Date.now(),
   y: 111
 }, {
   x: Date.now() + 500,
   y: 112
 }, ];

 var rss = [{
   x: Date.now() - 500,
   y: 44
 }, {
   x: Date.now(),
   y: 44
 }, {
   x: Date.now() + 500,
   y: 44
 }, ];

 const values = heapTotal.concat(heapUsed).concat(rss).reduce(function(prev, curr) {


   return {
     xMin: Math.min(prev.xMin, curr.x),
     xMax: Math.max(prev.xMax, curr.x),
     yMin: Math.min(prev.yMin, curr.y),
     yMax: Math.max(prev.yMax, curr.y),
   }

 }, {
   xMin: Number.MAX_SAFE_INTEGER,
   xMax: -1,
   yMin: Number.MAX_SAFE_INTEGER,
   yMax: -1
 });


 var vis = d3.select('#visualisation'),
   WIDTH = 1200,
   HEIGHT = 800,
   MARGINS = {
     top: 20,
     right: 20,
     bottom: 20,
     left: 50
   },
   xScale = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([values.xMin, values.xMax]),
   yScale = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([0.95*values.yMin, values.yMax]),
   xAxis = d3.svg.axis()
   .scale(xScale),
   yAxis = d3.svg.axis()
   .scale(yScale)
   .orient('left');

 vis.attr("width", WIDTH)
   .attr("height", HEIGHT)

 vis.append('svg:g')
   .attr('class', 'x axis')
   .attr('transform', 'translate(0,' + (HEIGHT - MARGINS.bottom) + ')')
   .call(xAxis);

 vis.append('svg:g')
   .attr('class', 'y axis')
   .attr('transform', 'translate(' + (MARGINS.left) + ',0)')
   .call(yAxis);

 var lineGen = d3.svg.line()
   .x(function(d) {
     return xScale(d.x);
   })
   .y(function(d) {
     return yScale(d.y);
   })
   .interpolate('basis');

 vis.append('svg:path')
   .attr('d', lineGen(heapUsed))
   .attr('stroke', 'green')
   .attr('stroke-width', 2)
   .attr('fill', 'none');

 vis.append('svg:path')
   .attr('d', lineGen(heapTotal))
   .attr('stroke', 'blue')
   .attr('stroke-width', 2)
   .attr('fill', 'none');

 vis.append('svg:path')
   .attr('d', lineGen(rss))
   .attr('stroke', 'red')
   .attr('stroke-width', 2)
   .attr('fill', 'none');
.axis path {
  fill: none;
  stroke: #777;
  shape-rendering: crispEdges;
}

.axis text {
  font-family: Lato;
  font-size: 13px;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<svg id='visualisation'></svg>

PS: I reduced the lower end of your scale a little bit (0.95*values.yMin), so the last line doesn't stay on top of the axis.

Upvotes: 1

Related Questions