I have three issues:
- How can I add ticks on the x axis so that they separate the grouped areas of the scatterplot (grouped columns per year)?
 - How can I place the years as text under the corresponding area of the scatterplot?
 - Is there a more elegant way to generate the scatterplot than to add the x position for each dot manually into the input data?
 
More context: I would like to visualize meetings of six organizations grouped by years from 2014 to 2020 with D3.js. Each dot of a scatterplot represents a meeting and the six colors match with the organizations. Currently I've added the x position within the scatterplot for each dot manually into the input data file, so that there are gaps between the years.
Current code:
import axios from "axios";
let meetings
let colors = { a: "brown", b: "orange", c: "red", d: "purple", e: "blue", f: "green" };
window.addEventListener("load", () => {
    initUI();
});
function initUI() {
    // parse input data
    axios
        .get("assets/example.txt")
        .then(async (res) => {
            var rawData = res.data
                .split("\n")
                // filter header row
                .filter((row) => (row.indexOf("label") >= 0 ? false : true))
                .map((row) => {
                    var items = row.split(";");
                    return {
                        label: items[0],
                        year: items[1],
                        xPosition: items[2],
                    };
                });
            meetings = addYPositionForAllOrganizations(rawData);
            scatterplot = await showScatterPlot(meetings);
        })
        .then(() => {
            // always executed
        });
}
// Add counter for amount of meetings for one organziation per year for y axis position
function addYPosition(organizationList) {    
    organizationList.sort((a, b) => (a.year > b.year) ? 1 : -1)
    var yPosition = 1;
    var year = 2014;
    organizationList.forEach(element => {
        if (year < element.year) {
            // reset counter for next year
            yPosition = 1;
        }
        element.yPosition = 0;
        element.yPosition += yPosition;
        yPosition++;
        year = element.year;
    });
}
function addYPositionForAllOrganizations(data) {
    let a = data.filter(row => row.label == "a");
    addYPosition(a);
    let b = data.filter(row => row.label == "b");
    addYPosition(b);
    let c = data.filter(row => row.label == "c");
    addYPosition(c);
    let d = data.filter(row => row.label == "d");
    addYPosition(d);
    let e = data.filter(row => row.label == "e");
    addYPosition(e);
    let f = data.filter(row => row.label == "f");
    addYPosition(f);
    return a.concat(b).concat(c).concat(d).concat(e).concat(f);
}
async function showScatterPlot(data) {
    let margin = { top: 10, right: 30, bottom: 100, left: 60 },
        width = 1000 - margin.left - margin.right,
        height = 400 - margin.top - margin.bottom;
    // append the svg object to the body of the page
    let svg = d3
        .select("#scatter-plot")
        .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 + ")");
    // Add x axis
    var x = d3.scaleLinear().domain([-2, 75]).range([0, width]);
    //FIXME this destroys ticks so that they are "invisible"
    var xAxis = d3.axisBottom(x).tickValues(2014, 2015, 2016, 2017, 2018, 2019, 2020);
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);
    // Add text label for x axis
    svg.append("text")
        .attr("transform", "translate(" + (width / 2) + "," + (height - (-100 / 3)) + ")")
        .attr("text-anchor", "middle")
        .style("font-family", "sans-serif")
        .text("Years 2014 - 2020");
    // Add y axis
    var y = d3.scaleLinear().domain([0.5, 10]).range([height, 0]);
    svg.append("g").attr("class", "y axis").call(d3.axisLeft(y));
    // Add text label for the y axis
    svg.append("text")
        .attr("transform", "rotate(-90)")
        .attr("y", 0 - margin.left)
        .attr("x", 0 - (height / 2))
        .attr("dy", "1em")
        .style("text-anchor", "middle")
        .style("font-family", "sans-serif")
        .text("Amount");
    // Add meetings as dots
    let meetings = svg.append('g')
        .selectAll("dot")
        .data(data)
        .enter()
        .append("circle")
        .attr("cx", function (d) { return x(d.xPosition); })
        .attr("cy", function (d) { return y(d.yPosition); })
        .attr("r", 5.5)
        .style("fill", getColorForMeeting);
    return { svg, x, y };    
}
function getColorForMeeting(data) {
    return colors[data.label];
}
<script src="https://d3js.org/d3.v4.js"></script>
<div id="scatter-plot"></div>
Extract of the input data file:

The running project can be investigated here.

