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.
Here's an example that resolves your three issues.