Search code examples
javascriptd3.jsgraphdashboard

Update graphs automatically in d3.js


Based on this tutorial I created two graphs with two metrics each. Every 5 seconds reads from a csv file showing cpu %user and cpu %system in the first graph and memory free - memory used in the second one.

The problem is that updates only the first line of the first graph. When I remove cpu %user it updates only the cpu %system. I cannot update all the metrics in both graphs with their axes as well.

Is there any obvious mistake here? Is there any other tutorial similar to what I'm trying to do in d3.js?

My code

<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */

body { font: 12px Arial;}

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

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


</style>
<body>

<!-- load the d3.js library -->    
<script src="http://d3js.org/d3.v3.min.js"></script>

<script>

// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 50},
    width = 350 - margin.left - margin.right,
    height = 250 - margin.top - margin.bottom;

// Parse the date / time
var parseTime = d3.time.format("%d-%m-%Y:%H:%M").parse;

// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);

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

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

// Define the line
var user = d3.svg.line()
    .x(function(d) { return x(d.timestamp); })
    .y(function(d) { return y(d.user); });

var system = d3.svg.line()
    .x(function(d) { return x(d.timestamp); })
    .y(function(d) { return y(d.system); });

var memFree = d3.svg.line()
    .x(function(d) { return x(d.timestamp); })
    .y(function(d) { return y(d.memFree); });

var memUsed = d3.svg.line()
    .x(function(d) { return x(d.timestamp); })
    .y(function(d) { return y(d.memUsed); });

// Adds the svg canvas
var chart1 = 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 + ")");
var chart2 = 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 + ")");

// Get the data
d3.csv("data.csv", function(error, data) {
    data.forEach(function(d) {
          d.timestamp = parseTime(d.timestamp);
          d.user = +d.user;
          d.system = +d.system;
          d.memFree = +d.memFree;
          d.memUsed = +d.memUsed;
    });

    var min = d3.min(data, function(d) { return Math.min(d.user, d.system); });
    x.domain(d3.extent(data, function(d) { return d.timestamp; }));
    y.domain([min, d3.max(data, function(d) { return Math.max(d.user, d.system); })]);

      chart1.append("path")
      .data([data])
      .attr("class", "userLine")
      .style("stroke", "green")
      .attr("d", user(data));

  chart1.append("path")
      .data([data])
      .attr("class", "systemLine")
      .style("stroke", "blue")
      .attr("d", system(data));

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

  // Add the Y Axis
  chart1.append("g")
      .call(yAxis);

  chart1.append("text")
    .attr("transform", "translate(" + (width/4) + "," + y(data[0].user) + ")")
    .attr("text-anchor", "start")
    .style("fill", "green")
    .text("%user");

  chart1.append("text")
    .attr("transform", "translate(" + (width/5) + "," + y(data[0].system) + ")")
    .attr("text-anchor", "start")
    .style("fill", "blue")
    .text("%system");

});

d3.csv("data.csv", function(error, data) {
    data.forEach(function(d) {
          d.timestamp = parseTime(d.timestamp);
          d.user = +d.user;
          d.system = +d.system;
          d.memFree = +d.memFree;
          d.memUsed = +d.memUsed;
    });

    // Scale the range of the data
  var min = d3.min(data, function(d) { return Math.min(d.memFree, d.memUsed); });
  x.domain(d3.extent(data, function(d) { return d.timestamp; }));
  y.domain([min, d3.max(data, function(d) { return Math.max(d.memFree, d.memUsed); })]);

  chart2.append("path")
      .data([data])
      .attr("class", "memFreeLine")
      .style("stroke", "green")
      .attr("d", memFree(data));

  chart2.append("path")
      .data([data])
      .attr("class", "memUsedLine")
      .style("stroke", "blue")
      .attr("d", memUsed(data));

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

  // Add the Y Axis
  chart2.append("g")
      .call(yAxis);

  chart2.append("text")
    .attr("transform", "translate(" + (width/4) + "," + y(data[0].memFree) + ")")
    .attr("text-anchor", "start")
    .style("fill", "green")
    .text("memFree");

  chart2.append("text")
    .attr("transform", "translate(" + (width/2) + "," + y(data[0].memUsed) + ")")
    .attr("text-anchor", "start")
    .style("fill", "blue")
    .text("memUsed");

});

setInterval(function() {
    updateChart1();
    updateChart2();
}, 5000); 

// ** Update data section (Called from the onclick)
function updateChart1() {
    // Get the data again
    d3.csv("data.csv", function(error, data) {
        data.forEach(function(d) {
          d.timestamp = parseTime(d.timestamp);
          d.user = +d.user;
          d.system = +d.system;
          d.memFree = +d.memFree;
          d.memUsed = +d.memUsed;
        });

    var min = d3.min(data, function(d) { return Math.min(d.user, d.system); });
    x.domain(d3.extent(data, function(d) { return d.timestamp; }));
    y.domain([min, d3.max(data, function(d) { return Math.max(d.user, d.system); })]);

    // Select the section we want to apply our changes to
    var chart1 = d3.select("svg").transition();

    // Make the changes
        chart1.select(".userLine")   // change the line
            .duration(750)
            .attr("d", user(data));
        chart1.select(".systemLine")   // change the line
            .duration(750)
            .attr("d", system(data));

        chart1.select(".x.axis") // change the x axis
            .duration(750)
            .call(xAxis);
        chart1.select(".y.axis") // change the y axis
            .duration(750)
            .call(yAxis);

    });
}

function updateChart2() {

    // Get the data again
    d3.csv("data.csv", function(error, data) {
        data.forEach(function(d) {
          d.timestamp = parseTime(d.timestamp);
          d.user = +d.user;
          d.system = +d.system;
          d.memFree = +d.memFree;
          d.memUsed = +d.memUsed;
        });

        // Scale the range of the data again 
    var min = d3.min(data, function(d) { return Math.min(d.memFree, d.memUsed); });
    x.domain(d3.extent(data, function(d) { return d.timestamp; }));
    y.domain([min, d3.max(data, function(d) { return Math.max(d.memFree, d.memUsed); })]);

    // Select the section we want to apply our changes to
    var chart2 = d3.select("svg").transition();

    // Make the changes
        chart2.select(".memFreeLine")   // change the line
            .duration(750)
            .attr("d", memFree(data));
        chart2.select(".memUsedLine")   // change the line
            .duration(750)
            .attr("d", memUsed(data));
        chart2.select(".x.axis") // change the x axis
            .duration(750)
            .call(xAxis);
        chart2.select(".y.axis") // change the y axis
            .duration(750)
            .call(yAxis);

    });
}

</script>
</body>

Solution

  • Here is the problem: you're selecting the same element inside both your updateChart functions:

    chart1.select(".line")//class here is "line"
            .duration(750)
            .attr("d", user(data));
    chart1.select(".line")//class here is "line" again!
            .duration(750)
            .attr("d", system(data));
    

    See? You're selecting the same class (.line) for both the user and the system data.

    Solution:

    In the initial painting, set different classes:

    chart1.append("path")
        .data([data])
        .attr("class", "userLine")//specific class
        .style("stroke", "green")
        .attr("d", user(data));
    
    chart1.append("path")
        .data([data])
        .attr("class", "systemLine")//specific class
        .style("stroke", "blue")
        .attr("d", system(data));
    

    And then, update each one individually:

    chart1.select(".userLine")//selecting different classes...  
            .duration(750)
            .attr("d", user(data));
    chart1.select(".systemLine")//selecting different classes...  
            .duration(750)
            .attr("d", system(data));