javascriptsvgd3.js

unable to get a hard-coded svg line on d3 chart


I'm trying to just get a simple line on a D3 chart without using an array of data (that will be the next step).

The x-axis is years from 2007 to 2023. The y-axis is numbers from 0.00 to 0.50.

I assume the data pairs I'll need will be x in a time formatted year and y as a number.

But when I put in the code to draw a line from one point to the other on the chart, nothing appears and I get no error message.

My example is running here: https://codepen.io/lschneiderman/pen/YPwGKaz

<div id="container"></div>
<script type="module">

const width = 1140;
const height = 400;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 50;
const marginLeft = 70;

//x-axis is years
const x = d3.scaleUtc()
    .domain([new Date("2007-01-01"), new Date("2023-01-01")])
    .range([marginLeft, width - marginRight]);

//y-axis is numbers between 0 and .5
const y = d3.scaleLinear()
    .domain([0, .5])
    .range([height - marginBottom, marginTop]);

const svg = d3.create("svg")
    .attr("width", width)
    .attr("height", height);

svg.append("g")
    .attr("transform", `translate(0,${height - marginBottom})`)
    .call(d3.axisBottom(x)
        .ticks(d3.utcYear.every(1))
);
    
svg.append("text") //label
    .attr("class", "x label")
    .attr("text-anchor", "end")
    .attr("x", width/2)
    .attr("y", height - 6)
    .text("Year");

svg.append("text") //label
    .attr("class", "y label")
    .attr("text-anchor", "end")
    .attr("x", -height/3)
    .attr("y", 6)
    .attr("dy", ".75em")
    .attr("transform", "rotate(-90)")
    .text("Percent");

svg.append("g")
    .attr("transform", `translate(${marginLeft},0)`)
    .call(d3.axisLeft(y));

const parseTime = d3.utcFormat("%Y");

svg.append("line")          // nothing appears
.style("stroke", "black")  
.attr("x1", parseTime(new Date("2007")))     // x position of the first end of the line
.attr("y1", 0.50)      // y position of the first end of the line
.attr("x2", parseTime(new Date("2008")))      // x position of the second end of the line
.attr("y2", 0.40);

container.append(svg.node());

Solution

  • First you need to parse your data using:

    data = data.map(d => ({
        date: d3.utcParse("%Y")(d.year), // Parse year string to Date object
        percent: +d.percent              // Convert percent string to Number
    }));
    

    Then you need to create a line generator:

    // 2. Define the Line Generator
    const line = d3.line()
        .x(d => x(d.date))    // Map the Date object to the x-scale
        .y(d => y(d.percent)); // Map the Number object to the y-scale
    

    Finally you need to append a path using the line generator:

    svg.append("path")
        .datum(data) // Bind the entire dataset to the path
        .attr("fill", "none")
        .attr("stroke", "steelblue")
        .attr("stroke-width", 1.5)
        .attr("d", line); // Use the line generator to create the 'd' attribute
    

    Full code:

    const width = 1140;
    const height = 400;
    const marginTop = 20;
    const marginRight = 20;
    const marginBottom = 50;
    const marginLeft = 70;
    
    var data = [ {'year':'2007', 'percent':'.05'}, {'year':'2008', 'percent':'.10'}, {'year':'2009', 'percent':'.05'} ]
    
    data = data.map(d => ({
        date: d3.utcParse("%Y")(d.year), // Parse year string to Date object
        percent: +d.percent              // Convert percent string to Number
    }));
    
    const x = d3.scaleUtc()
        .domain([new Date("2007-01-01"), new Date("2023-01-01")])
        .range([marginLeft, width - marginRight]);
    
    const y = d3.scaleLinear()
        .domain([0, .5])
        .range([height - marginBottom, marginTop]);
    
    const svg = d3.create("svg")
        .attr("width", width)
        .attr("height", height);
    
    svg.append("g")
        .attr("transform", `translate(0,${height - marginBottom})`)
        .call(d3.axisBottom(x)
            .ticks(d3.utcYear.every(1))
    );
        
    svg.append("text")
        .attr("class", "x label")
        .attr("text-anchor", "end")
        .attr("x", width/2)
        .attr("y", height - 6)
        .text("Year");
    
    svg.append("text")
        .attr("class", "y label")
        .attr("text-anchor", "end")
        .attr("x", -height/3)
        .attr("y", 6)
        .attr("dy", ".75em")
        .attr("transform", "rotate(-90)")
        .text("Percent");
    
    svg.append("g")
        .attr("transform", `translate(${marginLeft},0)`)
        .call(d3.axisLeft(y));
    
    const parseTime = d3.utcFormat("%Y");
    //console.log(parseTime(new Date("2007")));
    
    // 2. Define the Line Generator
    const line = d3.line()
        .x(d => x(d.date))    // Map the Date object to the x-scale
        .y(d => y(d.percent)); // Map the Number object to the y-scale
    
    // 3. Append the Line Path
    svg.append("path")
        .datum(data) // Bind the entire dataset to the path
        .attr("fill", "none")
        .attr("stroke", "steelblue")
        .attr("stroke-width", 1.5)
        .attr("d", line); // Use the line generator to create the 'd' attribute
    
    container.append(svg.node());
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
    <div id="container"></div>