Nominations Tracker
Track the progress of presidential nominations from submission through Senate confirmation
totalNoms = filtered.length
confirmedCount = filtered.filter(d => d.status === "Confirmed").length
withdrawnCount = filtered.filter(d => d.status === "Withdrawn").length
confirmedOnly = filtered.filter(d => d.status === "Confirmed" && d.days_to_resolution != null)
avgDaysConfirm = confirmedOnly.length > 0
? Math.round(confirmedOnly.reduce((sum, d) => sum + d.days_to_resolution, 0) / confirmedOnly.length)
: 0html`<div class="stat-grid">
<div class="stat-box">
<div class="stat-number">${totalNoms}</div>
<div class="stat-label">Total Nominations</div>
</div>
<div class="stat-box stat-sage">
<div class="stat-number">${confirmedCount}</div>
<div class="stat-label">Confirmed</div>
</div>
<div class="stat-box stat-rose">
<div class="stat-number">${withdrawnCount}</div>
<div class="stat-label">Withdrawn</div>
</div>
<div class="stat-box">
<div class="stat-number">${avgDaysConfirm}</div>
<div class="stat-label">Avg. Days to Confirm</div>
</div>
</div>`outcomeData = {
const counts = {};
for (const d of filtered) {
const s = d.status.startsWith("Pending") ? "Pending" : d.status;
counts[s] = (counts[s] || 0) + 1;
}
return Object.entries(counts).map(([status, count]) => ({status, count}));
}
doughnutColors = {
return {
"Confirmed": "#6B8F6B",
"Withdrawn": "#B85C5C",
"Returned": "#7A8290",
"Pending": "#5B7FA5"
};
}// Average days to confirmation by administration
avgByAdmin = {
const admins = [...new Set(data.map(d => d.administration))];
const result = [];
for (const admin of admins) {
const confirmed = filtered.filter(d => d.administration === admin && d.status === "Confirmed" && d.days_to_resolution != null);
if (confirmed.length > 0) {
const avg = Math.round(confirmed.reduce((s, d) => s + d.days_to_resolution, 0) / confirmed.length);
result.push({ administration: admin, avg_days: avg });
}
}
return result;
}Nomination Outcomes
{
const width = 300;
const height = 300;
const radius = Math.min(width, height) / 2;
const innerRadius = radius * 0.55;
const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("width", width)
.attr("height", height)
.style("font-family", "Nunito Sans, sans-serif");
const color = d3.scaleOrdinal()
.domain(["Confirmed", "Withdrawn", "Returned", "Pending"])
.range(["#6B8F6B", "#B85C5C", "#7A8290", "#5B7FA5"]);
const pie = d3.pie().value(d => d.count).sort(null).padAngle(0.02);
const arc = d3.arc().innerRadius(innerRadius).outerRadius(radius - 2);
const labelArc = d3.arc().innerRadius(radius * 0.78).outerRadius(radius * 0.78);
const arcs = pie(outcomeData);
svg.selectAll("path")
.data(arcs)
.join("path")
.attr("fill", d => color(d.data.status))
.attr("d", arc)
.attr("stroke", "#FEFCF8")
.attr("stroke-width", 2);
svg.selectAll("text")
.data(arcs)
.join("text")
.attr("transform", d => `translate(${labelArc.centroid(d)})`)
.attr("text-anchor", "middle")
.attr("font-size", "11px")
.attr("font-weight", "600")
.attr("fill", "#1E2D4F")
.text(d => d.data.count > 5 ? `${d.data.status} (${d.data.count})` : "");
// Center label
svg.append("text")
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.attr("font-weight", "700")
.attr("fill", "#1E2D4F")
.attr("dy", "-0.2em")
.text(totalNoms);
svg.append("text")
.attr("text-anchor", "middle")
.attr("font-size", "10px")
.attr("fill", "#7A8290")
.attr("dy", "1.2em")
.text("Total");
return svg.node();
}Avg. Days to Confirmation by Administration
Plot.plot({
marginLeft: 80,
height: 300,
x: { label: "Days" },
y: { label: null },
color: { range: ["#C06840"] },
marks: [
Plot.barX(avgByAdmin, {
y: "administration",
x: "avg_days",
fill: "#C06840",
sort: { y: "-x" }
}),
Plot.text(avgByAdmin, {
y: "administration",
x: "avg_days",
text: d => `${d.avg_days}d`,
dx: 5,
fill: "#4F5B6E",
fontSize: 11,
textAnchor: "start"
}),
Plot.ruleX([0])
]
})// Pipeline data
pipelineData = {
const total = filtered.length;
const confirmed = filtered.filter(d => d.status === "Confirmed").length;
const withdrawn = filtered.filter(d => d.status === "Withdrawn").length;
const returned = filtered.filter(d => d.status === "Returned").length;
const pendingCommittee = filtered.filter(d => d.status === "Pending - Committee").length;
const pendingFloor = filtered.filter(d => d.status === "Pending - Floor Vote").length;
// Simulate pipeline stages
const submitted = total;
const reachedCommittee = total - withdrawn;
const reachedFloor = reachedCommittee - returned - pendingCommittee;
const finalConfirmed = confirmed;
return {
stages: [
{ label: "Submitted", count: submitted },
{ label: "Committee", count: Math.max(reachedCommittee, 0) },
{ label: "Floor Vote", count: Math.max(reachedFloor, 0) },
{ label: "Confirmed", count: Math.max(finalConfirmed, 0) }
],
dropouts: [
{ label: `${withdrawn} withdrawn`, between: "0-1" },
{ label: `${returned + pendingCommittee} returned/pending`, between: "1-2" },
{ label: `${pendingFloor} pending floor`, between: "2-3" }
],
total: submitted
};
}Nomination Pipeline
{
const total = pipelineData.total || 1;
const stages = pipelineData.stages;
const colors = ["#5B7FA5", "#C06840", "#B8923E", "#6B8F6B"];
const container = d3.create("div");
// Bar
const bar = container.append("div")
.style("display", "flex")
.style("width", "100%")
.style("border-radius", "8px")
.style("overflow", "hidden")
.style("height", "48px");
stages.forEach((stage, i) => {
const pct = (stage.count / total) * 100;
bar.append("div")
.style("width", `${pct}%`)
.style("background", colors[i])
.style("display", "flex")
.style("align-items", "center")
.style("justify-content", "center")
.style("color", "#fff")
.style("font-size", "0.78rem")
.style("font-weight", "700")
.style("padding", "0 6px")
.style("min-width", "40px")
.text(`${stage.label} (${stage.count})`);
});
// Dropout labels
const dropouts = container.append("div")
.style("display", "flex")
.style("width", "100%")
.style("margin-top", "6px");
pipelineData.dropouts.forEach((d, i) => {
const pct = (stages[i].count / total) * 100;
dropouts.append("div")
.style("width", `${pct}%`)
.style("text-align", "center")
.style("font-size", "0.72rem")
.style("color", "#B85C5C")
.style("font-style", "italic")
.text(`↓ ${d.label}`);
});
return container.node();
}Data Table
Inputs.table(filtered, {
columns: ["nominee_name", "position", "department", "date_submitted", "status", "days_to_resolution"],
header: {
nominee_name: "Nominee",
position: "Position",
department: "Department",
date_submitted: "Date Submitted",
status: "Status",
days_to_resolution: "Days"
},
sort: "date_submitted",
reverse: true,
rows: 20
})