I am trying to replicate the effect of the burndown chart in the picture of action items which rely upon different fields, called Estimated Completion Date, Original Due Date, and Closed Date, and use them for Risk Mitigation events. I have a list of mitigation events for view and they use Baseline Date ( which corresponds to Original Due Date) and Closed Date which translates to Actual Completion Date for my table. I will have 3 series instead of 4.
As the burndown shows, cumulative effect is seen in the total open action items, which in my desired case which is correct.
What do I need to do to produce the effect in the burndown? The effect is when there is no change in the events based on Baseline Date or based on Actual Date, then their graph rises provided there are accumulating items, an when the actual data shows a date then the series starts to decline, which is done by subtracting from the total mitigation events for that date.
I’m getting a non decreasing graph for the burndown which doesn’t replicate the desired behavior.
Thank you for helping.
Desired Chart from Example
Current Model
https://jsfiddle.net/4ums7o19/
Shortened Snippet
<!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>