usage | system usage report | system usage png buffer | Search

The code uses the D3.js library to create a graph that displays system usage data over time, with the option to use a fallback data source if the primary data is missing. The graph is customized with a color palette and axis labels, and it displays a line for each group in the data.

Run example

npm run import -- "system usage graph"

system usage graph


import { D3Node } from 'd3-node'
const systemUsage = importer.import("system usage report")

async function graphUsage(data) {

  if(!data) {
    let usage = await systemUsage()
    let now = Date.now()
    data = usage.cpus.map((t, i) => ({
      n: t,
      year: now - i * 1000,
      name: 'cpu'
    })).concat(usage.memory.map((t, i) => ({
      n: t,
      year: now - i * 1000,
      name: 'mem'
    }))).sort((a, b) => a.name - b.name)

    console.log(data)
  }

  // set the dimensions and margins of the graph
  const margin = {top: 10, right: 30, bottom: 30, left: 60},
      width = 460 - margin.left - margin.right,
      height = 200 - margin.top - margin.bottom;

  const d3n = new D3Node(); // initializes D3 with container element 
  const d3 = d3n.d3;

  // append the svg object to the body of the page
  const svg = d3n.createSVG(
    width + margin.left + margin.right,
    height + margin.top + margin.bottom)
  /*const svg = d3.select("#my_dataviz")
    .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})`);

  // group the data: I want to draw one line per group
  const sumstat = d3.group(data, d => d.name); // nest function allows to group the calculation per level of a factor

  // Add X axis --> it is a date format
  const x = d3.scaleLinear()
    .domain(d3.extent(data, function(d) { return d.year; }))
    .range([ 0, width ]);
  svg.append("g")
    .attr("transform", `translate(0, ${height})`)
    .call(d3.axisBottom(x).ticks(5));

  // Add Y axis
  const y = d3.scaleLinear()
    .domain([0, 100 /* d3.max(data, function(d) { return +d.n; }) */])
    .range([ height, 0 ]);
  svg.append("g")
    .call(d3.axisLeft(y));

  // color palette
  const color = d3.scaleOrdinal()
    .range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf','#999999'])

  // Draw the line
  svg.selectAll(".line")
      .data(sumstat)
      .join("path")
        .attr("fill", "none")
        .attr("stroke", function(d){ return color(d[0]) })
        .attr("stroke-width", 1.5)
        .attr("d", function(d){
          return d3.line()
            .x(function(d) { return x(d.year); })
            .y(function(d) { return y(+d.n); })
            (d[1])
        })

  return d3n.svgString();
}

export default graphUsage

What the code could have been:

import { D3Node } from 'd3-node'
import systemUsage from './systemUsage'; // assuming systemUsage is a separate module

/**
 * Graphs system usage data.
 *
 * @param {Object[]} data - System usage data.
 * @returns {Promise<string>} SVG string representation of the graph.
 */
async function graphUsage(data) {
  if (!data) {
    // TODO: Consider caching system usage data to prevent repeated calls.
    const usage = await systemUsage();
    const now = Date.now();
    data = usage.cpus
     .map((t, i) => ({ n: t, year: now - i * 1000, name: 'cpu' }))
     .concat(usage.memory.map((t, i) => ({ n: t, year: now - i * 1000, name:'mem' }))
      ).sort((a, b) => a.name.localeCompare(b.name)); // use localeCompare for string comparison
    console.log(data);
  }

  const margin = { top: 10, right: 30, bottom: 30, left: 60 };
  const width = 460 - margin.left - margin.right;
  const height = 200 - margin.top - margin.bottom;

  const d3n = new D3Node();
  const d3 = d3n.d3;

  const svg = d3n.createSVG(
    width + margin.left + margin.right,
    height + margin.top + margin.bottom
  ).append("g")
   .attr("transform", `translate(${margin.left},${margin.top})`);

  // Group data by name
  const sumstat = d3.group(data, (d) => d.name);

  // Set up scales
  const x = d3.scaleTime() // Use scaleTime for date scales
   .domain(d3.extent(data, (d) => d.year))
   .range([0, width]);
  const y = d3.scaleLinear()
   .domain([0, Math.max(...data.map((d) => d.n))]) // Use Math.max for domain calculation
   .range([height, 0]);

  // Add axes
  svg.append("g")
   .attr("transform", `translate(0, ${height})`)
   .call(d3.axisBottom(x).ticks(5));
  svg.append("g")
   .call(d3.axisLeft(y));

  // Color palette
  const color = d3.scaleOrdinal()
   .range([
      '#e41a1c',
      '#377eb8',
      '#4daf4a',
      '#984ea3',
      '#ff7f00',
      '#ffff33',
      '#a65628',
      '#f781bf',
      '#999999'
    ]);

  // Draw the line
  const lines = svg.selectAll(".line")
   .data(sumstat)
   .join("path")
   .attr("fill", "none")
   .attr("stroke", (d) => color(d[0]))
   .attr("stroke-width", 1.5)
   .attr("d", (d) => d3.line()
     .x((d) => x(d.year))
     .y((d) => y(+d.n))
      (d[1])
    );

  return d3n.svgString();
}

export default graphUsage;

Code Breakdown

Importing Modules and Data

import { D3Node } from 'd3-node'
const systemUsage = importer.import('system usage report')

The code imports the D3Node module from the d3-node library, which is used for creating a D3.js context. It also imports a function systemUsage from a module named system usage report.

Function graphUsage

async function graphUsage(data) {
  //...
}

The graphUsage function is defined as an asynchronous function that takes a single argument data.

Handling Missing Data

if (!data) {
  let usage = await systemUsage()
  //...
}

If the data argument is falsy, the function awaits the result of calling systemUsage() and stores the result in usage. It then generates data by mapping over the cpus and memory properties of the usage object.

Setting up the Graph

const margin = { top: 10, right: 30, bottom: 30, left: 60 },
  width = 460 - margin.left - margin.right,
  height = 200 - margin.top - margin.bottom;

const d3n = new D3Node();
const d3 = d3n.d3;

// append the svg object to the body of the page
const svg = d3n.createSVG(
  width + margin.left + margin.right,
  height + margin.top + margin.bottom)
  //...

The code sets up the margins, width, and height of the graph. It creates a new instance of D3Node and gets the D3.js context. It then creates an SVG element with the specified width and height.

Grouping Data and Drawing the Graph

const sumstat = d3.group(data, d => d.name);

// Add X axis --> it is a date format
const x = d3.scaleLinear()
 .domain(d3.extent(data, function(d) { return d.year; }))
 .range([ 0, width ]);
svg.append("g")
 .attr("transform", `translate(0, ${height})`)
 .call(d3.axisBottom(x).ticks(5));

// Add Y axis
const y = d3.scaleLinear()
 .domain([0, 100 /* d3.max(data, function(d) { return +d.n; }) */])
 .range([ height, 0 ]);
svg.append("g")
 .call(d3.axisLeft(y));

// color palette
const color = d3.scaleOrdinal()
 .range(['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf','#999999'])

// Draw the line
svg.selectAll(".line")
 .data(sumstat)
 .join(
    enter => enter.append("path")
     .attr("class", "line")
     .attr("fill", "none")
     .attr("stroke", (d, i) => color(i))
     .attr("stroke-width", 1.5)
     .attr("d", d => d[0].x0 + " " + d[0].y0 + " " + d[1].x1 + " " + d[1].y1),
    update => update,
    exit => exit.remove()
  )

The code groups the data by the name property and sets up the X and Y axes. It defines a color palette and draws a line for each group in the data.

Note

The last part of the code is incomplete and contains a typo. The variable sumsta should be sumstat.