Code Snip: D3 Timeline Graph

Saturday, Dec 6, 2025

Code Snip: D3 Timeline Graph

I made a cool timeline graph for my resume page.

import * as d3 from "d3";
import { useEffect, useRef } from "react";

export default function Timeline({
    data,
    width = 1080,
    height = 1080,
}) {
    const svgRef = useRef();

    const sortedData = [...data].sort((a, b) => a.start_date.getTime() - b.start_date.getTime());
    const length_of_data = sortedData.length;

    useEffect(() => {
        const color = d3.scaleOrdinal(d3.schemeCategory10);

        const svg = d3.select(svgRef.current)
            .attr("width", width)
            .attr("height", height)
            .attr("viewBox", [0, 0, width, height])
            .attr("style", "max-width: 100%; height: auto; font: 20px sans-serif;");

        svg.selectAll("*").remove();


        const xScale = d3.scaleTime().domain([
            d3.min(sortedData, d => d.start_date),
            d3.max(sortedData, d => d.end_date)
        ]).range([0, width]);

        const yScale = d3.scaleLinear([0, length_of_data], [0, height-100]);
        const yScaleDiff = Math.abs(yScale(1) - yScale(0));

        const axisbar = svg.append("g")
            .attr("transform", `translate(0, 50)`)
            .call(d3.axisTop(xScale))
            .attr("font-size", '20px');

        const timebars = svg.append("g")
            .selectAll()
            .data(sortedData)
            .enter()
            .append("g");

        timebars
            .append("line")
            .attr("y1", d => yScale(sortedData.findIndex(r => r.title === d.title))+100)
            .attr("y2", d => yScale(sortedData.findIndex(r => r.title === d.title))+100)
            .attr("x1", d => xScale(d.start_date))
            .attr("x2", d => xScale(d.end_date))
            .attr("stroke", d => color(d.group))
            .attr("stroke-width", yScaleDiff / 4);

        timebars
            .append("text")
            .text(d => `${d.title}`)
            .attr("x", d => xScale(d.start_date)+5 < width - (width/3) ? xScale(d.start_date)+5 : width- 5)
            .attr("y", d => yScale(sortedData.findIndex(r => r.title === d.title))+105)
            .attr('text-anchor', d => xScale(d.start_date)+5 < width - (width/3) ? 'start' : 'end');


        timebars
            .append("title")
            .text(d => `${d.title}`);

        return () => {};


    }, [data, width, height]);

    return (
        <svg ref={svgRef}></svg>
    );
}