Skip to content

js d3 chart with timescale

I am a newbie coding js and d3, and I have the code below, I want to display the dates in array on the x axis instead of numbers. I tried many ways to display the dates; most of ways display the dates with no line and data with errors in the HTML console, that it could not read the data type of the dates it only allows numbers.

<!DOCTYPE html>
<meta charset="utf-8">



<body>
</body>

<!-- Load in the d3 library -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>

// 2. Use the margin convention practice 
var margin = {top: 200, right: 300, bottom: 400, left: 300}
  , width = window.innerWidth - margin.left - margin.right // Use the window's width 
  , height = window.innerHeight - margin.top - margin.bottom; // Use the window's height


var parseDate = d3.timeParse("%Y-%m-%d");

// 8. An array of objects of length N. Each object has key -> value pair, the key being "y" and the value is a random number
var xdata = [
    ["2021-08-01",(d3.randomUniform(100)()).toFixed(1)],
    ["2021-08-02",(d3.randomUniform(100)()).toFixed(1)],
    ["2021-08-03",(d3.randomUniform(100)()).toFixed(1)],
    ["2021-08-04",(d3.randomUniform(100)()).toFixed(1)],
    ["2021-08-05",(d3.randomUniform(100)()).toFixed(1)],
    ["2021-08-06",(d3.randomUniform(100)()).toFixed(1)],
    ["2021-08-07",(d3.randomUniform(100)()).toFixed(1)],
    ["2021-08-08",(d3.randomUniform(100)()).toFixed(1)],
    ["2021-08-09",(d3.randomUniform(100)()).toFixed(1)],
    ["2021-08-10",(d3.randomUniform(100)()).toFixed(1)],
    ["2021-08-11",(d3.randomUniform(100)()).toFixed(1)]
];



var n = xdata.length;
var max_value = 0;
var col1 = 0;
for (let i = 0; i < n; i++) {
xdata[i][0] = parseDate(xdata[i][0]);
//console.log(xdata[i][0]);
col1 = parseFloat(xdata[i][1])
if (col1 > max_value)
    max_value = col1


}
console.log(max_value);


// 5. X scale will use the index of our data
var xScale = d3.scaleLinear()
  .domain([0,n-1 ]) // input
    .range([0, width]); // output


// 6. Y scale will use the randomly generate number 
var yScale = d3.scaleLinear()
    .domain([0, max_value]) // input 
    .range([height, max_value]); // output 

// 7. d3's line generator
var line = d3.line()
    .x(function(d, i) { return xScale(i); }) // set the x values for the line generator
    .y(function(d) { return yScale(d.y); }) // set the y values for the line generator 
    .curve(d3.curveMonotoneX) // apply smoothing to the line



//var dataset = d3.range(n).map(function(d) { return {"y": (d3.randomUniform(100)()).toFixed(1) } })

var dataset = xdata.map(function(d) {
      return {
         x: parseDate(d[0]),
         y: d[1]
      };
      
  });

// 1. Add the SVG to the page and employ #2
var svg = 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 + ")");

// 3. Call the x axis in a group tag
svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom

// 4. Call the y axis in a group tag
svg.append("g")
    .attr("class", "y axis")
    .call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft

// 9. Append the path, bind the data, and call the line generator 
svg.append("path")
    .datum(dataset) // 10. Binds data to the line 
    .attr("fill", "none")
    .attr("stroke", "steelblue")
    .attr("stroke-width", 3)
    .attr("d", line); // 11. Calls the line generator 

// 12. Appends a circle for each datapoint 
svg.selectAll(".dot")
    .data(dataset)
  .enter().append("circle") // Uses the enter().append() method
    .style("fill","steelblue")
    .attr("cx", function(d, i) { return xScale(i) })
    .attr("cy", function(d) { return yScale(d.y) })
    .attr("r", 5);

// 13. Appends data text to each datapoint  
svg.selectAll(".text")
    .data(dataset)
    .enter().append("text")
    .style("fill", "red") 
    .attr("x", function(d, i) { return xScale(i) - 5 })
    .attr("y", function(d) { return yScale(d.y) - 20 })
    .text(function(d) { return d.y });
    

</script>

Thanks for your answers in advance

Answer

I would suggest parsing the strings into Date objects right away and then using d3.scaleTime for the x scale. Here is an example below.

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

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

    <script>
      // set up

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

      const width = 500 - margin.left - margin.right;
      const height = 250 - margin.top - margin.bottom;

      const svg = d3.select('#chart')
        .append('svg')
          .attr('width', width + margin.left + margin.right)
          .attr('height', height + margin.top + margin.bottom);

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

      // data

      const parseDate = d3.timeParse("%Y-%m-%d");

      const dataset = [
        ["2021-08-01",(d3.randomUniform(100)()).toFixed(1)],
        ["2021-08-02",(d3.randomUniform(100)()).toFixed(1)],
        ["2021-08-03",(d3.randomUniform(100)()).toFixed(1)],
        ["2021-08-04",(d3.randomUniform(100)()).toFixed(1)],
        ["2021-08-05",(d3.randomUniform(100)()).toFixed(1)],
        ["2021-08-06",(d3.randomUniform(100)()).toFixed(1)],
        ["2021-08-07",(d3.randomUniform(100)()).toFixed(1)],
        ["2021-08-08",(d3.randomUniform(100)()).toFixed(1)],
        ["2021-08-09",(d3.randomUniform(100)()).toFixed(1)],
        ["2021-08-10",(d3.randomUniform(100)()).toFixed(1)],
        ["2021-08-11",(d3.randomUniform(100)()).toFixed(1)]
      ].map(d => ({ date: parseDate(d[0]), value: parseFloat(d[1]) }));

      const minMaxDate = d3.extent(dataset, d => d.date);
      const maxValue = d3.max(dataset, d => d.value);

      // scales

      const x = d3.scaleTime()
          .domain(minMaxDate)
          .range([0, width]);

      const y = d3.scaleLinear()
          .domain([0, maxValue])
          .range([height, 0]);

      // line generator

      const line = d3.line()
          .x(d => x(d.date))
          .y(d => y(d.value))
          .curve(d3.curveMonotoneX);


      // axes

      g.append("g")
          .attr("class", "x axis")
          .attr("transform", `translate(0,${height})`)
          .call(d3.axisBottom(x));
      
      g.append("g")
          .attr("class", "y axis")
          .call(d3.axisLeft(y));


      // line

      g.append('path')
          .datum(dataset)
          .attr('fill', 'none')
          .attr('stroke', 'steelblue')
          .attr('stroke-width', 3)
          .attr('d', line);

      // circles

      g.selectAll('.dot')
        .data(dataset)
        .join('circle')
          .attr('fill', 'steelblue')
          .attr('cx', d => x(d.date))
          .attr('cy', d => y(d.value))
          .attr('r', 5);

      // text labels

      g.selectAll('.text')
        .data(dataset)
        .join('text')
          .attr('fill', 'red')
          .attr('x', d => x(d.date) - 5)
          .attr('y', d => y(d.value) - 20)
          .text(d => d.value);
    </script>
</body>
</html>