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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more