function generateEventTimeline() {
const svg = d3.create("svg")
.attr("class", "gantt-chart")
.attr("width", width)
.attr("height", HEIGHT);
let zoomTransform = d3.zoomIdentity;
const zoom = d3.zoom()
.extent([[0, 0], [width, HEIGHT]])
.scaleExtent([1, 8])
.on("zoom", ({transform}) => {
zoomTransform = transform;
draw();
});
function draw() {
svg.selectAll("g.axis,g.grid,g.events,g.legend,text.now").remove();
const scaleX = zoomTransform.rescaleX(timeScale);
function drawAxesAndGrid() {
const timeAxis = d3.axisBottom(scaleX)
.ticks(d3.timeMonth.every(12))
.tickFormat(tickFormat);
const timeGrid = d3.axisBottom(scaleX)
.ticks(d3.timeMonth.every(4))
.tickFormat(tickFormat)
.tickSize(HEIGHT - MARGIN.top - MARGIN.bottom);
const xAxisContainer = svg.append("g")
.attr("class", "axis x")
.attr("transform", `translate(${MARGIN.left},${MARGIN.top})`)
.call(timeAxis);
const yAxisContainer = svg.append("g")
.attr("class", "grid x")
.attr("transform", `translate(${MARGIN.left},${MARGIN.top})`)
.call(timeGrid)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line")
.attr("stroke-opacity", 0.3)
.attr("stroke-dasharray", "2,2"));
// add a little bit of context to show where we are right now
svg.append("text")
.attr("class", "now")
.attr("transform", `translate(${PADDING.left + MARGIN.left},5)`)
.attr("x", scaleX(Date.now()) - 3)
.attr("y", 2 * FONT_SIZE)
.attr("font-size", FONT_SIZE)
.text("👆 now");
}
function drawEvents() {
// draw the actual events
const g = svg.append("g")
.attr("class", "events")
.attr("transform", `translate(${MARGIN.left},${PADDING.top})`);
// for each event, draw a rectangular box that indicates the start and end dates,
// color coded either by the type of event or the institution.
const event = g.selectAll("g.event").data(events).join("g")
.attr("class", "event")
.attr("transform", d => `translate(${scaleX(new Date(d.start))},${scaleY(d.start)})`);
event.append("rect")
.attr("class", "event")
.attr("width", d => scaleX(new Date(d.end)) - scaleX(new Date(d.start)))
.attr("height", eventHeight)
.attr("fill", d => scaleColor(d[activeColorCategory]));
event.append("line")
.attr("class", "leader")
.attr("stroke", d => scaleColor(d[activeColorCategory]))
.attr("x1", d => scaleX(new Date(d.end)) - scaleX(new Date(d.start)))
.attr("x2", d => scaleX(new Date(d.end)) - scaleX(new Date(d.start)))
.attr("y1", d => 0)
.attr("y2", d => eventHeight);
event.append("line")
.attr("class", "readoff")
.attr("stroke", d => scaleColor(d[activeColorCategory]))
.attr("stroke-width", 1)
.attr("stroke-dasharray", "2,2")
.attr("x1", d => scaleX(new Date(d.end)) - scaleX(new Date(d.start)))
.attr("x2", d => scaleX(new Date(d.end)) - scaleX(new Date(d.start)))
.attr("y1", d => -scaleY(d.start) - PADDING.top + 5)
.attr("y2", d => -eventHeight + scaleY.bandwidth());
event.append("text")
.attr("class", "label")
.attr("text-anchor", "end")
.attr("x", d => scaleX(new Date(d.end)) - scaleX(new Date(d.start)))
.attr("dx", -5)
.attr("y", -5)
.attr("font-size", FONT_SIZE)
.text(d => `${d.title}`);
// draw the collections as circles into each event
const collectionsG = event.append("g")
.attr("class", "collections")
.attr("fill", d => scaleColor(d[activeColorCategory]))
.attr("transform", d => `translate(${scaleX(new Date(d.end)) - scaleX(new Date(d.start))},0)`)
const collection = collectionsG.selectAll("g.collection").data(d => d[COLLECTIONS_KEY]).join("g")
.attr("class", "collection");
collection.append("circle")
.attr("class", "collection")
.attr("r", r)
.attr("cx", (d, i) => -(i+.75) * r * 2.3)
.attr("cy", eventHeight / 2);
collection.append("text")
.attr("x", 10)
.attr("font-size", FONT_SIZE)
.text(d => d);
}
function drawLegend() {
// draw the legend for the color scale
const legend = svg.append("g")
.attr("class", "legend")
.attr("transform", `translate(${MARGIN.left}, ${HEIGHT - LEGEND_HEIGHT})`);
// add background to make legend more readable over data
legend.append("rect")
.attr("class", "background")
.attr("width", LEGEND_WIDTH)
.attr("height", LEGEND_HEIGHT)
.attr("fill", "rgba(255,255,255,0.3)");
const label = legend.selectAll("g.label").data(colorScaleDomain).join("g")
.attr("class", "label")
.attr("transform", (d, i) => `translate(${LEGEND_MARGIN}, ${i * COLOR_SIZE * 2})`);
label.append("rect")
.attr("width", COLOR_SIZE)
.attr("height", COLOR_SIZE)
.attr("fill", scaleColor);
label.append("text")
.attr("class", "label")
.attr("x", COLOR_SIZE * 1.5)
.attr("y", COLOR_SIZE)
.attr("font-size", FONT_SIZE)
.attr("font-style", "italic")
.text(d => d);
}
drawAxesAndGrid();
drawEvents();
drawLegend();
}
draw();
return svg.call(zoom).node();
}