Public
Edited
Feb 23, 2023
7 forks
Importers
20 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function generateEventTimeline() {
// TODO: allow filtering/emphasizing technologies of interest -> "when did I use technology xyz?"
// create a canvas element
const svg = d3.create("svg")
.attr("class", "gantt-chart")
.attr("width", width)
.attr("height", HEIGHT);

// zoom behavior along the x axis.
let zoomTransform = d3.zoomIdentity;
const zoom = d3.zoom()
.extent([[0, 0], [width, HEIGHT]])
.scaleExtent([1, 8])
.on("zoom", ({transform}) => {
zoomTransform = transform;
draw();
});

function draw() {
// the main container for the data
svg.selectAll("g.axis,g.grid,g.events,g.legend,text.now").remove();
const scaleX = zoomTransform.rescaleX(timeScale);

// add axes and grid to the background
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();
}
Insert cell
Insert cell
Insert cell
generateStyles = function() {
const css = d3.create("style").node()
css.innerHTML = `
svg.gantt-chart text {
font-family: sans-serif;
}
svg.gantt-chart .event rect.event {
fill-opacity: 0.3;
stroke: none;
}
svg.gantt-chart .event:hover rect.interval {
fill-opacity: 0.5;
}
svg.gantt-chart .event line.leader {
stroke-width: 2px;
}
svg.gantt-chart .event line.readoff {
stroke-width: 1px;
opacity: 0;
transition: opacity 0.15s ease;
}
svg.gantt-chart .event:hover line.readoff {
opacity: 1;
}
svg.gantt-chart g.collection circle {
stroke: none;
}
svg.gantt-chart g.collection text {
font-weight: bold;
opacity: 0;
transition: opacity 0.15s ease;
}
svg.gantt-chart g.collection:hover circle {
stroke: black;
stroke-width: 2;
}
svg.gantt-chart g.collection:hover text {
opacity: 1;
}
`;
return css;
}
Insert cell
generateStyles()
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more