viewof chart = {
const margin = { top: 100, right: 30, bottom: 80, left: 120 },
width = 1200 - margin.left - margin.right,
fullHeight = 1 * rawData.length,
visibleHeight = 800;
const topShift = 20;
const outer = d3.create("div")
.style("width", "100%")
.style("height", `${visibleHeight}px`)
.style("overflow", "auto")
.style("border", "1px solid #ccc")
.style("font-family", "sans-serif");
const svg = d3.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", fullHeight + margin.top + margin.bottom + topShift);
outer.append(() => svg.node());
rawData.forEach(d => d.transfer_date = new Date(d.transfer_date));
const x = d3.scaleTime()
.domain(d3.extent(rawData, d => d.transfer_date))
.range([0, width]);
const xAxis = d3.axisBottom(x)
.ticks(d3.timeMonth.every(2))
.tickFormat(d3.timeFormat("%b %Y"));
const xAxisTopGroup = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
.call(xAxis);
const chartGroup = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top + topShift})`);
const xAxisGroup = svg.append("g")
.attr("transform", `translate(${margin.left},${fullHeight + margin.top + topShift})`)
.call(xAxis);
const yAxisGroup = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top + topShift})`);
const projects = Array.from(new Set(rawData.map(d => d.project_key)));
const y = d3.scaleBand()
.domain(projects)
.range([0, fullHeight])
.padding(0.3);
const yAxis = d3.axisLeft(y);
yAxisGroup.call(yAxis);
const color = d3.scaleOrdinal(d3.schemeSet2);
const bars = chartGroup.selectAll("rect")
.data(rawData.filter(d => d.regular_hours >= 0))
.join("rect")
.attr("x", d => x(d.transfer_date))
.attr("y", d => y(d.project_key))
.attr("width", 10)
.attr("height", y.bandwidth())
.attr("fill", d => color(d.Category));
// Tooltip styling
const tooltip = html`<div style="position:absolute; background:white; border:1px solid #ccc; padding:8px 12px; display:none; box-shadow:0 2px 6px rgba(0,0,0,0.15); font-size:13px;"></div>`;
document.body.appendChild(tooltip);
bars
.on("mouseover", (event, d) => {
tooltip.style.display = "block";
tooltip.innerHTML = `
<strong>Project:</strong> ${d.project_key}<br>
<strong>Date:</strong> ${d3.timeFormat("%Y-%m-%d")(d.transfer_date)}<br>
<strong>Hours:</strong> ${d.regular_hours}<br>
<strong>Comment:</strong> ${d.comment || "(none)"}
`;
})
.on("mousemove", (event) => {
tooltip.style.left = event.pageX + 10 + "px";
tooltip.style.top = event.pageY + "px";
})
.on("mouseout", () => {
tooltip.style.display = "none";
});
// Legend
const categories = Array.from(new Set(rawData.map(d => d.Category)));
const legend = svg.append("g")
.attr("class", "legend")
.attr("transform", `translate(${margin.left}, 20)`);
categories.forEach((cat, i) => {
const xOffset = i % 4 * 180;
const yOffset = Math.floor(i / 4) * 20;
legend.append("rect")
.attr("x", xOffset)
.attr("y", yOffset)
.attr("width", 14)
.attr("height", 14)
.attr("fill", color(cat));
legend.append("text")
.attr("x", xOffset + 20)
.attr("y", yOffset + 12)
.style("font-size", "13px")
.text(cat);
});
// Chart title
svg.append("text")
.attr("x", margin.left + width / 2)
.attr("y", 12)
.attr("text-anchor", "middle")
.style("font-size", "18px")
.style("font-weight", "bold")
.text("Employee 2185 - Time Allocation by Project");
// Axis labels
svg.append("text")
.attr("x", margin.left + width / 2)
.attr("y", fullHeight + margin.top + topShift + 50)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Time (transfer_date)");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -margin.top - topShift - fullHeight / 2)
.attr("y", 20)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Project (project_key)");
return outer.node();
}