<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Buildup and Burndown Charts</title>
<!-- Include D3.js -->
<script src="https://d3js.org/d3.v6.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
}
.chart-container {
margin: 50px;
}
.axis-label {
font-size: 12px;
font-weight: bold;
}
.legend {
font-size: 12px;
}
.line {
fill: none;
stroke-width: 2px;
}
</style>
</head>
<body>
<div class="chart-container">
<h2>Buildup Chart</h2>
<svg id="buildupChart" width="900" height="500"></svg>
</div>
<div class="chart-container">
<h2>Burndown Chart</h2>
<svg id="burndownChart" width="900" height="500"></svg>
</div>
<script>
(function() {
// -------------------------------
// 2. Parse and Preprocess the Data
// -------------------------------
const parseDate = d3.timeParse("%Y-%m-%d");
const eventsData = [
{
"eventid": 1,
"riskid": null,
"eventtitle": "Component Sourcing",
"eventstatus": "Complete",
"eventownerid": 1,
"actualdate": "2024-09-01",
"actuallikelihood": 4,
"actualtechnical": 5,
"actualschedule": 3,
"actualcost": 4,
"baselinedate": "2024-09-01",
"baselinelikelihood": 4,
"baselinetechnical": 5,
"baselineschedule": 3,
"baselinecost": 4,
"scheduledate": null,
"scheduledlikelihood": null,
"scheduledtechnical": null,
"scheduledschedule": null,
"scheduledcost": null
},
{
"eventid": 2,
"riskid": null,
"eventtitle": "Supplier Negotiation",
"eventstatus": "Complete",
"eventownerid": 1,
"actualdate": "2024-09-25",
"actuallikelihood": 3,
"actualtechnical": 5,
"actualschedule": 4,
"actualcost": 4,
"baselinedate": "2024-09-05",
"baselinelikelihood": 3,
"baselinetechnical": 5,
"baselineschedule": 4,
"baselinecost": 4,
"scheduledate": null,
"scheduledlikelihood": null,
"scheduledtechnical": null,
"scheduledschedule": null,
"scheduledcost": null
},
{
"eventid": 3,
"riskid": null,
"eventtitle": "Component Delivery",
"eventstatus": "Complete",
"eventownerid": 1,
"actualdate": "2024-10-10",
"actuallikelihood": 2,
"actualtechnical": 3,
"actualschedule": 2,
"actualcost": 2,
"baselinedate": "2024-09-10",
"baselinelikelihood": 2,
"baselinetechnical": 3,
"baselineschedule": 2,
"baselinecost": 2,
"scheduledate": null,
"scheduledlikelihood": null,
"scheduledtechnical": null,
"scheduledschedule": null,
"scheduledcost": null
},
{
"eventid": 1,
"riskid": null,
"eventtitle": "Integration Testing",
"eventstatus": "Complete",
"eventownerid": 2,
"actualdate": "2024-09-05",
"actuallikelihood": 5,
"actualtechnical": 4,
"actualschedule": 4,
"actualcost": 3,
"baselinedate": "2024-09-05",
"baselinelikelihood": 5,
"baselinetechnical": 4,
"baselineschedule": 4,
"baselinecost": 3,
"scheduledate": null,
"scheduledlikelihood": null,
"scheduledtechnical": null,
"scheduledschedule": null,
"scheduledcost": null
},
{
"eventid": 1,
"riskid": null,
"eventtitle": "Machinery Maintenance",
"eventstatus": "Complete",
"eventownerid": 3,
"actualdate": "2024-09-02",
"actuallikelihood": 3,
"actualtechnical": 4,
"actualschedule": 5,
"actualcost": 4,
"baselinedate": "2024-09-02",
"baselinelikelihood": 3,
"baselinetechnical": 4,
"baselineschedule": 5,
"baselinecost": 4,
"scheduledate": null,
"scheduledlikelihood": null,
"scheduledtechnical": null,
"scheduledschedule": null,
"scheduledcost": null
},
{
"eventid": 2,
"riskid": null,
"eventtitle": "Operational Check",
"eventstatus": "Baseline",
"eventownerid": 3,
"actualdate": null,
"actuallikelihood": null,
"actualtechnical": null,
"actualschedule": null,
"actualcost": null,
"baselinedate": "2024-09-30",
"baselinelikelihood": 2,
"baselinetechnical": 3,
"baselineschedule": 3,
"baselinecost": 3,
"scheduledate": "2024-09-30",
"scheduledlikelihood": 2,
"scheduledtechnical": 3,
"scheduledschedule": 3,
"scheduledcost": 3
},
{
"eventid": 3,
"riskid": null,
"eventtitle": "Final Review",
"eventstatus": "Baseline",
"eventownerid": 6,
"actualdate": null,
"actuallikelihood": null,
"actualtechnical": null,
"actualschedule": null,
"actualcost": null,
"baselinedate": "2024-10-31",
"baselinelikelihood": 2,
"baselinetechnical": 1,
"baselineschedule": 2,
"baselinecost": 1,
"scheduledate": "2024-10-31",
"scheduledlikelihood": 2,
"scheduledtechnical": 2,
"scheduledschedule": 2,
"scheduledcost": 1
},
{
"eventid": 1,
"riskid": null,
"eventtitle": "Worker Adjustment",
"eventstatus": "Complete",
"eventownerid": 5,
"actualdate": "2024-09-03",
"actuallikelihood": 4,
"actualtechnical": 3,
"actualschedule": 5,
"actualcost": 4,
"baselinedate": "2024-09-03",
"baselinelikelihood": 4,
"baselinetechnical": 3,
"baselineschedule": 5,
"baselinecost": 4,
"scheduledate": null,
"scheduledlikelihood": null,
"scheduledtechnical": null,
"scheduledschedule": null,
"scheduledcost": null
},
{
"eventid": 2,
"riskid": null,
"eventtitle": "Strike Negotiation",
"eventstatus": "Complete",
"eventownerid": 5,
"actualdate": "2024-09-05",
"actuallikelihood": 3,
"actualtechnical": 2,
"actualschedule": 1,
"actualcost": 3,
"baselinedate": "2024-09-10",
"baselinelikelihood": 3,
"baselinetechnical": 2,
"baselineschedule": 1,
"baselinecost": 3,
"scheduledate": null,
"scheduledlikelihood": null,
"scheduledtechnical": null,
"scheduledschedule": null,
"scheduledcost": null
},
{
"eventid": 1,
"riskid": null,
"eventtitle": "Regulatory Submission",
"eventstatus": "Complete",
"eventownerid": 6,
"actualdate": "2024-09-04",
"actuallikelihood": 3,
"actualtechnical": 2,
"actualschedule": 4,
"actualcost": 5,
"baselinedate": "2024-09-04",
"baselinelikelihood": 3,
"baselinetechnical": 2,
"baselineschedule": 4,
"baselinecost": 5,
"scheduledate": null,
"scheduledlikelihood": null,
"scheduledtechnical": null,
"scheduledschedule": null,
"scheduledcost": null
},
{
"eventid": 2,
"riskid": null,
"eventtitle": "Approval Follow-Up",
"eventstatus": "Active",
"eventownerid": 6,
"actualdate": null,
"actuallikelihood": null,
"actualtechnical": null,
"actualschedule": null,
"actualcost": null,
"baselinedate": "2024-09-04",
"baselinelikelihood": 3,
"baselinetechnical": 4,
"baselineschedule": 3,
"baselinecost": 4,
"scheduledate": "2024-09-18",
"scheduledlikelihood": 3,
"scheduledtechnical": 3,
"scheduledschedule": 3,
"scheduledcost": 4
},
{
"eventid": 3,
"riskid": null,
"eventtitle": "Regulatory Review",
"eventstatus": "Active",
"eventownerid": 6,
"actualdate": null,
"actuallikelihood": null,
"actualtechnical": null,
"actualschedule": null,
"actualcost": null,
"baselinedate": "2024-09-15",
"baselinelikelihood": 3,
"baselinetechnical": 4,
"baselineschedule": 3,
"baselinecost": 4,
"scheduledate": "2024-10-09",
"scheduledlikelihood": 2,
"scheduledtechnical": 4,
"scheduledschedule": 4,
"scheduledcost": 5
}
]
const events = eventsData.map(d => ({
eventid: d.eventid,
eventtitle: d.eventtitle,
eventstatus: d.eventstatus,
eventownerid: d.eventownerid,
actualDate: d.actualdate ? parseDate(d.actualdate) : null,
baselineDate: d.baselinedate ? parseDate(d.baselinedate) : null,
scheduledDate: d.scheduledate ? parseDate(d.scheduledate) : null
}));
// -------------------------------
// 3. Prepare Data for Buildup Chart
// -------------------------------
// Collect all dates with their types
let buildupDates = [];
events.forEach(event => {
if (event.actualDate) {
buildupDates.push({ date: event.actualDate, type: 'actual' });
}
if (event.baselineDate) {
buildupDates.push({ date: event.baselineDate, type: 'baseline' });
}
// You can include scheduled dates if needed
});
// Sort all dates in ascending order
buildupDates.sort((a, b) => a.date - b.date);
// Aggregate cumulative counts
let cumulativeTotal = 0;
let cumulativeBaseline = 0;
let cumulativeActual = 0;
const buildupDataMap = d3.rollup(
buildupDates,
v => d3.rollups(v, v => v.length, d => d.type),
d => d.date
);
// Get a sorted array of unique dates
const uniqueBuildupDates = Array.from(buildupDataMap.keys()).sort((a, b) => a - b);
const buildupData = uniqueBuildupDates.map(date => {
const typeCounts = buildupDataMap.get(date) || new Map();
let actuals = 0;
let baselines = 0;
typeCounts.forEach(([type, count]) => {
if (type === 'actual') actuals += count;
if (type === 'baseline') baselines += count;
});
cumulativeTotal += actuals + baselines;
cumulativeActual += actuals;
cumulativeBaseline += baselines;
return {
date: date,
total: cumulativeTotal,
baseline: cumulativeBaseline,
actual: cumulativeActual
};
});
// -------------------------------
// 4. Prepare Data for Burndown Chart
// -------------------------------
// The burndownData will have:
// - total: same as buildupData.total
// - remainingBaseline: buildupData.baseline - buildupData.actual
// - remainingActual: buildupData.total - buildupData.actual
const burndownData = buildupData.map(d => {
const remainingBaseline = d.baseline - d.actual;
const remainingActual = d.total - d.actual;
return {
date: d.date,
total: d.total,
remainingBaseline: remainingBaseline >= 0 ? remainingBaseline : 0,
remainingActual: remainingActual >= 0 ? remainingActual : 0
};
});
// -------------------------------
// 5. Function to Create Buildup Chart
// -------------------------------
function createBuildupChart(data) {
const svg = d3.select("#buildupChart"),
margin = { top: 50, right: 150, bottom: 50, left: 60 },
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Set up scales
const x = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([0, width]);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => Math.max(d.total, d.baseline, d.actual)) * 1.1])
.range([height, 0]);
// Define lines
const lineTotal = d3.line()
.x(d => x(d.date))
.y(d => y(d.total));
const lineBaseline = d3.line()
.x(d => x(d.date))
.y(d => y(d.baseline));
const lineActual = d3.line()
.x(d => x(d.date))
.y(d => y(d.actual));
// Add X axis
g.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x));
// Add Y axis
g.append("g")
.call(d3.axisLeft(y));
// Add lines
g.append("path")
.datum(data)
.attr("class", "line")
.attr("stroke", "red")
.attr("d", lineTotal);
g.append("path")
.datum(data)
.attr("class", "line")
.attr("stroke", "#5555dd")
.attr("d", lineBaseline);
g.append("path")
.datum(data)
.attr("class", "line")
.attr("stroke", "orange")
.attr("d", lineActual);
// Add Labels
g.append("text")
.attr("x", width / 2)
.attr("y", height + margin.bottom - 10)
.attr("text-anchor", "middle")
.attr("class", "axis-label")
.text("Date");
g.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -margin.left + 15)
.attr("text-anchor", "middle")
.attr("class", "axis-label")
.text("Cumulative Count");
// Add Legend
const legend = svg.append("g")
.attr("transform", `translate(${width + margin.left + 20},${margin.top})`);
const legendData = [
{ name: "Total Events", color: "red" },
{ name: "Baseline Events", color: "#5555dd" },
{ name: "Actual Events", color: "orange" }
];
legend.selectAll("rect")
.data(legendData)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", (d, i) => i * 25)
.attr("width", 18)
.attr("height", 18)
.style("fill", d => d.color);
legend.selectAll("text")
.data(legendData)
.enter()
.append("text")
.attr("x", 25)
.attr("y", (d, i) => i * 25 + 9)
.attr("dy", ".35em")
.text(d => d.name);
}
// -------------------------------
// 6. Function to Create Burndown Chart
// -------------------------------
function createBurndownChart(data) {
const svg = d3.select("#burndownChart"),
margin = { top: 50, right: 150, bottom: 50, left: 60 },
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Set up scales
const x = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([0, width]);
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => d.total) * 1.1]) // Scale based on total to ensure alignment
.range([height, 0]);
// Define lines
const lineTotal = d3.line()
.x(d => x(d.date))
.y(d => y(d.total));
const lineRemainingBaseline = d3.line()
.x(d => x(d.date))
.y(d => y(d.remainingBaseline));
const lineRemainingActual = d3.line()
.x(d => x(d.date))
.y(d => y(d.remainingActual));
// Add X axis
g.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(x));
// Add Y axis
g.append("g")
.call(d3.axisLeft(y));
// Add lines
// Total line (matches Buildup's cumulative total)
g.append("path")
.datum(data)
.attr("class", "line")
.attr("stroke", "red")
.attr("d", lineTotal);
// Remaining Baseline line
g.append("path")
.datum(data)
.attr("class", "line")
.attr("stroke", "#5555dd")
.attr("d", lineRemainingBaseline);
// Remaining Actual line
g.append("path")
.datum(data)
.attr("class", "line")
.attr("stroke", "green")
.attr("d", lineRemainingActual);
// Add Labels
g.append("text")
.attr("x", width / 2)
.attr("y", height + margin.bottom - 10)
.attr("text-anchor", "middle")
.attr("class", "axis-label")
.text("Date");
g.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -margin.left + 15)
.attr("text-anchor", "middle")
.attr("class", "axis-label")
.text("Remaining Open Events");
// Add Legend
const legend = svg.append("g")
.attr("transform", `translate(${width + margin.left + 20},${margin.top})`);
const legendData = [
{ name: "Total Events", color: "red" },
{ name: "Baseline Remaining", color: "#5555dd" },
{ name: "Actual Remaining", color: "green" }
];
legend.selectAll("rect")
.data(legendData)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", (d, i) => i * 25)
.attr("width", 18)
.attr("height", 18)
.style("fill", d => d.color);
legend.selectAll("text")
.data(legendData)
.enter()
.append("text")
.attr("x", 25)
.attr("y", (d, i) => i * 25 + 9)
.attr("dy", ".35em")
.text(d => d.name);
}
// -------------------------------
// 7. Initialize the Charts
// -------------------------------
// Create Buildup Chart
createBuildupChart(buildupData);
// Create Burndown Chart
createBurndownChart(burndownData);
})();
</script>
</body>
</html>