Published
Edited
Sep 4, 2020
Insert cell
Insert cell
{
const svg = d3.select(DOM.svg(width, height));
svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("fill", "white");
let g = svg.append("g")
.attr("class", "general")
.attr("transform", `translate(${margin.top}, ${margin.left})`);
let colorScale = d3.scaleOrdinal(d3.schemeCategory10);
let xAxis = d3.axisBottom(xScale)
.tickFormat(d3.timeFormat("%b-%d"));
let yAxis = d3.axisLeft(yScale)
.ticks(tasks.length);
// Axis variables
let lineDash = "5, 10";
let lineColor = "#D0D0D0";
// X Axis and Ticks
g.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0, ${chartHeight - margin.top})`)
.call(xAxis);
// Date Divider lines
g.append("g")
.attr("class", ".date-dividers")
.selectAll(".divider")
.data(xScale.ticks().slice(1)).enter()
.append("line")
.attr("class", "divider")
.attr("x1", (d) => xScale(d))
.attr("x2", (d) => xScale(d))
.attr("y1", 1)
.attr("y2", chartHeight - margin.bottom)
.attr("stroke", lineColor)
.attr("stroke-dasharray", lineDash);
// Y Axis and Ticks
g.append("g")
.attr("class", "y-axis")
.attr("transform", `translate(${margin.left + 50}, 0)`)
.call(yAxis);
g.append("g")
.attr("class", "cell-dividers")
.selectAll(".divider")
.data(tasks).enter()
.append("line")
.attr("class", "divider")
.attr("x1", margin.left + 50)
.attr("x2", chartWidth)
.attr("y1", (d) => yScale(d.task))
.attr("y2", (d) => yScale(d.task))
.attr("stroke", lineColor)
.attr("stroke-dasharray", lineDash);
let eventsGroup = g.append("g")
.attr("class", "task-period-group")
.attr("transform", `translate(${margin.left + margin.right}, ${margin.top})`);
let cells = eventsGroup.selectAll(".task")
.data(tasks).enter()
.append("g")
.attr("class", "task")
.attr("transform", (d, i) => yScale(d.task) - 3)
svg.selectAll(".lanes")
.data(allPeriods)
.join("rect")
.attr("class", "lanes")
.attr("x", (d)=> d.startX)
.attr("width", (d) => d.endX - d.startX)
.attr("y", (d)=> yScale(d.task)+ (sizePerLane -2))
.attr("transform", (d)=>{
const lane = d.index +1 >= maxlanes ? sizePerLane * maxlanes : sizePerLane * d.index
return `translate(${0}, ${lane - d.index * (sizePerLane)})`
} )
.attr("height", sizePerLane -2)
.attr("fill", (d, i)=> colorScale(d.task) )
.attr("stroke", "black")
.attr("stroke-width", 1)
// .on("mouseover", (d, i) => { console.log(d) });
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
yScale = d3.scaleBand()
.domain(tasks.map((task) => task.task))
.range([0, chartHeight - margin.bottom]);
Insert cell
xScale = d3.scaleTime()
.domain(dateRange)
.range([margin.left + 50, chartWidth]);
Insert cell
dateRange = {
let lowestDate = new Date();
let highestDate = new Date(tasks[0].periods[0][1]);
tasks.forEach((task) => {
task.periods.forEach((period) => {
if (new Date(period[0]).getTime() < lowestDate.getTime()) lowestDate = new Date(period[0]);
if (new Date(period[1]).getTime() > highestDate.getTime()) highestDate = new Date([period[1]]);
});
});
lowestDate.setHours(0);
highestDate.setHours(23);
highestDate.setMinutes(59);
highestDate.setSeconds(59);
highestDate.setMilliseconds(999);
return [lowestDate, highestDate];
}
Insert cell
Insert cell
Insert cell
maxlanes = {
return tasks.reduce((acc, taskObj)=>{
// sort dates in each task
const sortedPeriods = taskObj.periods.sort((a,b)=>{new Date(a[0]) - new Date(b[0])})
// convert to pixel values
const timespan =
sortedPeriods.map((dRange, index)=>({startX: xScale(new Date (dRange[0])), endX: xScale(new Date(dRange[1]))
}))
// calculate total number of lanes per task
let prev
const totalLanesForTask = timespan.reduce((laneNumbers, timeObj, index)=>{
if(index === 0) {
prev = timeObj
} else {
// start of next before the end of prev?
if(prev.endX >= timeObj.startX) {
laneNumbers += 1
}
}
return laneNumbers
}, 1)
// compare max lanes for all tasks (acc)
if(totalLanesForTask > acc) {
acc = totalLanesForTask
}
return acc
}, [])
}
Insert cell
allPeriods ={
return tasks.reduce((acc, taskObj)=>{
const sortedPeriods = taskObj.periods.sort((a,b)=>{new Date(a[0]) - new Date(b[0])})
// convert to pixel values
const timespan =
sortedPeriods.map((dRange, index)=>({startX: xScale(new Date (dRange[0])), endX: xScale(new Date(dRange[1])), task: taskObj.task, index
}))
acc = [...acc, ...timespan]
return acc
},[])
}
Insert cell
Insert cell
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