// Use this to read a CSV from a URL > which were stored through the targets pipeline
myf_overview_data = {
const response = await fetch("https://files.grant-witness.us/nih_myf_overview.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}Multiyear Funding over Time for NIH
The following interactive funding curves are adapted from Jeremy Berg. These visualizations demonstrate the cumulative outyear funds for multiyear funded grants across the NIH in fiscal year (FY) 2021 to 2026. We also visualize the percent of these outyear funds out of the total funds obligated to these grants. Note that we are only looking at new and competitive renewal grants (Type 1, 2, 4, and 9) for these calculations and the multiyear funded curves.
For more information about how we created these graphs, see our Methods section (specifically the “Multiyear Funding” subsection under the “NIH” section).
For more discussions about these funding curves, read the related articles published by Nature and Science.
myf_IC_data = {
const response = await fetch("https://files.grant-witness.us/nih_myf_IC_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
myf_IC_data_filtered = myf_IC_data.filter(function(grant) {
return grant.IC === inst_centers;
});myf_mechanism_data = {
const response = await fetch("https://files.grant-witness.us/nih_myf_mechanism_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
myf_mechanism_data_filtered = myf_mechanism_data.filter(function(grant) {
return grant.Mechanism === mechanism;
});myf_activity_data = {
const response = await fetch("https://files.grant-witness.us/nih_myf_activity_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
myf_activity_data_filtered = myf_activity_data.filter(function(grant) {
return grant.Activity === activity;
});myf_specific_activity_data = {
const response = await fetch("https://files.grant-witness.us/nih_myf_specific_activity.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
myf_specific_activity_data_filtered = myf_specific_activity_data.filter(function(grant) {
return grant.Activity === specific;
});// Define a helper to normalize today's date to the year 2016/2017
todayNormalized = {
const now = new Date();
const month = now.getMonth(); // 0 = Jan, 9 = Oct
const year = month >= 9 ? 2016 : 2017;
// Create the date and force the specific year
const d = new Date(now);
d.setFullYear(year);
return d;
}usdFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
// For the "Overview" graphs
// 1. Cumulative 2021 - 2024 Values
cum_cost_avg = {
// 1. Filter by years
const filtered = myf_overview_data.filter(d =>
[2021, 2022, 2023, 2024].includes(d.Original_Year)
);
// 2. Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
// 3. Summarize (Mean) and Round
const mean = latestEntries.reduce((sum, d) => sum + d.Cum_Outyear, 0) / latestEntries.length;
return Math.round(mean);
}
formatted_cum_cost_avg = usdFormatter.format(cum_cost_avg / 1e9) + "B"
// 2. Cumulative 2025 values
cum_cost_2025 = {
// Filter by years
const filtered = myf_overview_data.filter(d =>
[2025].includes(d.Original_Year)
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
return latestEntries[0].Cum_Outyear;
}
formatted_cum_cost_2025 = usdFormatter.format(cum_cost_2025 / 1e9) + "B"
// 3. Cumulative 2026 Values
last_cum_cost_2026 = {
// Filter by years
const filtered = myf_overview_data.filter(d =>
[2026].includes(d.Original_Year)
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
return latestEntries[0].Cum_Outyear;
}
formatted_last_cum_cost_2026 = usdFormatter.format(last_cum_cost_2026 / 1e9) + "B"Overview
Click or move your cursor along the graph to compare data across FY 2021 to 2026.
The tooltip will provide information about a given day compared to previous fiscal years. The black dotted line represents today’s date, while the bolded orange line demonstrates the trends for FY 2025 and the bolded red line demonstrates the current trend for FY 2026, with the remaining lines demonstrating trends from FY 2021-2024. The FY runs from October 1st of the previous year through September 30th of the current year.
Plot.plot({
marginLeft: 50,
width: 928,
// Add labels to the axes here
x: {
label: "Award Date",
tickFormat: "%b", // Shows 'Jan', 'Feb', etc. Use "%B" for full names
},
y: {
label: "Obligations",
tickFormat: d => `$${(d / 1e9).toFixed(1)}B`,
grid: true // Optional: makes it easier to read values
},
// specify colors here for outstanding lines
color: {
domain: [2021, 2022, 2023, 2024, 2025, 2026], // The specific values in your data
range: ["#6605a6", "#0b72b3", "#2A9D8F", "#e96ab6", "#F4A261", "#E63946"] // The specific colors you want (e.g., Red and Blue)
},
marks: [
// Filter for years that ARE NOT 2025 or 2026 (as an example)
Plot.lineY(myf_overview_data.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Outyear",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
myf_overview_data,
Plot.pointerX(Plot.binX({}, { x: "NoA_date", thresholds: 1000, insetTop: 20 }))
),
// The Current Day line
Plot.ruleX([todayNormalized], {
stroke: "black",
strokeDasharray: "4", // 4px dash, 4px gap
strokeOpacity: 1 // Makes it look a bit more subtle
}),
Plot.text([todayNormalized], {
x: d => d,
text: d => "Today",
textAnchor: "start",
fontSize: 20,
dx: 10,
dy: -50,
fill: "black"
}),
//Tooltip code
Plot.tip(
myf_overview_data,
Plot.pointerX(
Plot.binX(
{title: (v) => v},
{
x: "NoA_date",
thresholds: 1000,
render(index, scales, values, dimensions, context) {
const g = d3.select(context.ownerSVGElement).append("g");
const [i] = index;
if (i !== undefined) {
const myf_overview_data = values.title[i];
g.attr(
"transform",
`translate(${Math.min(
values.x1[i],
dimensions.width - dimensions.marginRight - 200
)}, 20)`
).append(() =>
Plot.plot({
marginTop: 20,
height: 150,
width: 150,
axis: null,
y: {domain: scales.scales.color.domain},
marks: [
Plot.frame({ fill: "white", stroke: "currentColor" }),
Plot.dot(myf_overview_data, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(myf_overview_data, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(myf_overview_data, {
y: "Original_Year",
text: d => {
if (d.Cum_Outyear >= 1000000000) {
// Format as Billions if 1,000,000,000 or greater
return `$${(d.Cum_Outyear / 1000000000).toFixed(1)}B`;
} else {
// Format as Millions otherwise
return `$${(d.Cum_Outyear / 1000000).toFixed(1)}M`;
}
},
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(myf_overview_data, {
frameAnchor: "top-left",
dy: -20,
text: (d) => {
// 1. Convert the string "2026-03-30" into a JS Date object
const dateObj = new Date(d.NoA_date);
// 2. Format it to "Month Day" (e.g., "March 30")
return dateObj.toLocaleString("en-US", {
month: "long",
day: "numeric"
});
},
fontSize: 16,
fontWeight: "bold"
})
]
})
);
}
return g.node();
}
}
)
)
),
Plot.ruleY([0])
]
})Plot.plot({
marginLeft: 50,
width: 928,
// Add labels to the axes here
x: {
label: "Award Date",
tickFormat: "%b", // Shows 'Jan', 'Feb', etc. Use "%B" for full names
},
y: {
label: "Outyear Funding (as percent, %)",
grid: true // Optional: makes it easier to read values
},
// specify colors here for outstanding lines
color: {
domain: [2021, 2022, 2023, 2024, 2025, 2026], // The specific values in your data
range: ["#6605a6", "#0b72b3", "#2A9D8F", "#e96ab6", "#F4A261", "#E63946"] // The specific colors you want (e.g., Red and Blue)
},
marks: [
// Filter for years that ARE NOT 2025 or 2026 (as an example)
Plot.lineY(myf_overview_data.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Outyear_Share",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
myf_overview_data,
Plot.pointerX(Plot.binX({}, { x: "NoA_date", thresholds: 1000, insetTop: 20 }))
),
// The Current Day line
Plot.ruleX([todayNormalized], {
stroke: "black",
strokeDasharray: "4", // 4px dash, 4px gap
strokeOpacity: 1 // Makes it look a bit more subtle
}),
Plot.text([todayNormalized], {
x: d => d,
text: d => "Today",
textAnchor: "start",
fontSize: 20,
dx: 10,
dy: -50,
fill: "black"
}),
//Tooltip code
Plot.tip(
myf_overview_data,
Plot.pointerX(
Plot.binX(
{title: (v) => v},
{
x: "NoA_date",
thresholds: 1000,
render(index, scales, values, dimensions, context) {
const g = d3.select(context.ownerSVGElement).append("g");
const [i] = index;
if (i !== undefined) {
const myf_overview_data = values.title[i];
g.attr(
"transform",
`translate(${Math.min(
values.x1[i],
dimensions.width - dimensions.marginRight - 200
)}, 20)`
).append(() =>
Plot.plot({
marginTop: 20,
height: 150,
width: 150,
axis: null,
y: {domain: scales.scales.color.domain},
marks: [
Plot.frame({ fill: "white", stroke: "currentColor" }),
Plot.dot(myf_overview_data, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(myf_overview_data, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(myf_overview_data, {
y: "Original_Year",
text: "Cum_Outyear_Share",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text([myf_overview_data[0]], {
frameAnchor: "top-left",
dy: -20,
text: (d) => {
// 1. Convert the string "2026-03-30" into a JS Date object
const dateObj = new Date(d.NoA_date);
// 2. Format it to "Month Day" (e.g., "March 30")
return dateObj.toLocaleString("en-US", {
month: "long",
day: "numeric"
});
},
fontSize: 16,
fontWeight: "bold"
})
]
})
);
}
return g.node();
}
}
)
)
),
Plot.ruleY([0])
]
})- The total funding obligated through multiyear funding mechanisms was more in FY 2025, which was .
- This is compared to the average amount obligated through multiyear funding across FY 2021 through 2024, which was .
- So far, the cumulative multiyear funding in FY 2026 is .
By Institutes and Centers (ICs)
Use the following drop-down to compare FY trends in award types based on NIH Institute or Center.
viewof inst_centers = Inputs.select(
new Map([
["Advancing Translational Sciences" ,"TR"],
["Aging" ,"AG"],
["Alcohol Abuse and Alcoholism" ,"AA"],
["Allergy and Infectious Diseases" ,"AI"],
["Arthritis and Musculoskeletal and Skin Diseases" ,"AR"],
["Biomedical Imaging and Bioengineering" ,"EB"],
["Cancer Institute" ,"CA"],
["Child Health and Human Development" ,"HD"],
["Complementary and Integrative Health" ,"AT"],
["Deafness and Other Communication Disorders" ,"DC"],
["Dental and Craniofacial Research" ,"DE"],
["Diabetes and Digestive and Kidney Diseases" ,"DK"],
["Drug Abuse" ,"DA"],
["Environmental Health Sciences" ,"ES"],
["Eye" ,"EY"],
["Fogarty International Center", "TW"],
["General Medical Sciences" ,"GM"],
["Heart, Lung, and Blood Institute" ,"HL"],
["Human Genome Research" ,"HG"],
["Library of Medicine" ,"LM"],
["Mental Health" ,"MH"],
["Minority Health and Health Disparities" ,"MD"],
["Neurological Disorders and Stroke" ,"NS"],
["Nursing Research" ,"NR"],
["Office of Director", "OD"]
]),
{label: "Institute or Center:", value: "TR"} // 'value' sets the default
)inst_award_avg = {
// 1. Filter by years
const filtered = myf_IC_data_filtered.filter(d =>
[2021, 2022, 2023, 2024].includes(d.Original_Year)
);
// 2. Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
// 3. Summarize (Mean) and Round
const mean = latestEntries.reduce((sum, d) => sum + d.Cum_Outyear, 0) / latestEntries.length;
return Math.round(mean);
}
formatted_inst_award_avg = usdFormatter.format(inst_award_avg / 1e6) + "M"
// 2. Cumulative 2025 values
inst_award_2025 = {
// Filter by years
const filtered = myf_IC_data_filtered.filter(d =>
[2025].includes(d.Original_Year)
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
return latestEntries[0].Cum_Outyear;
}
formatted_inst_award_2025 = usdFormatter.format(inst_award_2025 / 1e6) + "M"
// 3. Cumulative 2026 Values
inst_award_2026 = {
// Filter by years
const filtered = myf_IC_data_filtered.filter(d =>
[2026].includes(d.Original_Year)
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
return latestEntries[0].Cum_Outyear;
}
formatted_inst_award_2026 = usdFormatter.format(inst_award_2026 / 1e6) + "M"Plot.plot({
marginLeft: 50,
width: 928,
// Add labels to the axes here
x: {
label: "Award Date",
tickFormat: "%b", // Shows 'Jan', 'Feb', etc. Use "%B" for full names
},
y: {
label: "Obligations",
tickFormat: d => {
if (Math.abs(d) >= 1e9) {
return `$${(d / 1e9).toFixed(1)}B`; // Billions
} else {
return `$${(d / 1e6).toFixed(1)}M`; // Millions
}
},
grid: true // Optional: makes it easier to read values
},
// specify colors here for outstanding lines
color: {
domain: [2021, 2022, 2023, 2024, 2025, 2026], // The specific values in your data
range: ["#6605a6", "#0b72b3", "#2A9D8F", "#e96ab6", "#F4A261", "#E63946"] // The specific colors you want (e.g., Red and Blue)
},
marks: [
// Filter for years that ARE NOT 2025 or 2026 (as an example)
Plot.lineY(myf_IC_data_filtered.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Outyear",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
myf_IC_data_filtered,
Plot.pointerX(Plot.binX({}, { x: "NoA_date", thresholds: 1000, insetTop: 20 }))
),
// The Current Day line
Plot.ruleX([todayNormalized], {
stroke: "black",
strokeDasharray: "4", // 4px dash, 4px gap
strokeOpacity: 1 // Makes it look a bit more subtle
}),
Plot.text([todayNormalized], {
x: d => d,
text: d => "Today",
textAnchor: "start",
fontSize: 20,
dx: 10,
dy: -50,
fill: "black"
}),
//Tooltip code
Plot.tip(
myf_IC_data_filtered,
Plot.pointerX(
Plot.binX(
{title: (v) => v},
{
x: "NoA_date",
thresholds: 1000,
render(index, scales, values, dimensions, context) {
const g = d3.select(context.ownerSVGElement).append("g");
const [i] = index;
if (i !== undefined) {
const myf_IC_data_filtered = values.title[i];
g.attr(
"transform",
`translate(${Math.min(
values.x1[i],
dimensions.width - dimensions.marginRight - 200
)}, 20)`
).append(() =>
Plot.plot({
marginTop: 20,
height: 150,
width: 150,
axis: null,
y: {domain: scales.scales.color.domain},
marks: [
Plot.frame({ fill: "white", stroke: "currentColor" }),
Plot.dot(myf_IC_data_filtered, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(myf_IC_data_filtered, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(myf_IC_data_filtered, {
y: "Original_Year",
text: d => new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
notation: 'compact',
compactDisplay: 'short'
}).format(d.Cum_Outyear),
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(myf_IC_data_filtered, {
frameAnchor: "top-left",
dy: -20,
text: (d) => {
// 1. Convert the string "2026-03-30" into a JS Date object
const dateObj = new Date(d.NoA_date);
// 2. Format it to "Month Day" (e.g., "March 30")
return dateObj.toLocaleString("en-US", {
month: "long",
day: "numeric"
});
},
fontSize: 16,
fontWeight: "bold"
})
]
})
);
}
return g.node();
}
}
)
)
),
Plot.ruleY([0])
]
})Plot.plot({
marginLeft: 50,
width: 928,
// Add labels to the axes here
x: {
label: "Award Date",
tickFormat: "%b", // Shows 'Jan', 'Feb', etc. Use "%B" for full names
},
y: {
label: "Outyear Funding (as percent, %)",
grid: true // Optional: makes it easier to read values
},
// specify colors here for outstanding lines
color: {
domain: [2021, 2022, 2023, 2024, 2025, 2026], // The specific values in your data
range: ["#6605a6", "#0b72b3", "#2A9D8F", "#e96ab6", "#F4A261", "#E63946"] // The specific colors you want (e.g., Red and Blue)
},
marks: [
// Filter for years that ARE NOT 2025 or 2026 (as an example)
Plot.lineY(myf_IC_data_filtered.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Outyear_Share",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
myf_IC_data_filtered,
Plot.pointerX(Plot.binX({}, { x: "NoA_date", thresholds: 1000, insetTop: 20 }))
),
// The Current Day line
Plot.ruleX([todayNormalized], {
stroke: "black",
strokeDasharray: "4", // 4px dash, 4px gap
strokeOpacity: 1 // Makes it look a bit more subtle
}),
Plot.text([todayNormalized], {
x: d => d,
text: d => "Today",
textAnchor: "start",
fontSize: 20,
dx: 10,
dy: -50,
fill: "black"
}),
//Tooltip code
Plot.tip(
myf_IC_data_filtered,
Plot.pointerX(
Plot.binX(
{title: (v) => v},
{
x: "NoA_date",
thresholds: 1000,
render(index, scales, values, dimensions, context) {
const g = d3.select(context.ownerSVGElement).append("g");
const [i] = index;
if (i !== undefined) {
const myf_IC_data_filtered = values.title[i];
g.attr(
"transform",
`translate(${Math.min(
values.x1[i],
dimensions.width - dimensions.marginRight - 200
)}, 20)`
).append(() =>
Plot.plot({
marginTop: 20,
height: 150,
width: 150,
axis: null,
y: {domain: scales.scales.color.domain},
marks: [
Plot.frame({ fill: "white", stroke: "currentColor" }),
Plot.dot(myf_IC_data_filtered, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(myf_IC_data_filtered, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(myf_IC_data_filtered, {
y: "Original_Year",
text: "Cum_Outyear_Share",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text([myf_IC_data_filtered[0]], {
frameAnchor: "top-left",
dy: -20,
text: (d) => {
// 1. Convert the string "2026-03-30" into a JS Date object
const dateObj = new Date(d.NoA_date);
// 2. Format it to "Month Day" (e.g., "March 30")
return dateObj.toLocaleString("en-US", {
month: "long",
day: "numeric"
});
},
fontSize: 16,
fontWeight: "bold"
})
]
})
);
}
return g.node();
}
}
)
)
),
Plot.ruleY([0])
]
})These visualizations demonstrate that there is a difference in the number of awards and amount of funding obligated in FY 2025 to 2026 when filtering by IC.
inst_label = {
inst_centers; // This "listens" for changes to the dropdown
const select = viewof inst_centers.querySelector("select");
return select.options[select.selectedIndex].text;
}For example, based on your selection of Awards in , the following totals are determined:
- The total funding obligated through multiyear funding mechanisms was more in FY 2025, which was .
- This is compared to the average amount obligated through multiyear funding across FY 2021 through 2024, which was
- So far, the cumulative multiyear funding in FY 2026 is grants have been awarded.
To compare these totals with a different category, select a different “Institute or Center” from the drop-down button.
By Mechanism
Use the following drop-down to compare FY trends in award types based on NIH Mechanism
viewof mechanism = Inputs.select(
new Map([
["SBIR/STTR" ,"SBIR/STTR"],
["Non-SBIR/STTR" ,"Non-SBIR/STTR"],
["Training, Individual" ,"Training, Individual"],
["Training, Institutional" ,"Training, Institutional"],
["Research Centers" ,"Research Centers"],
["Other Research-Related" ,"Other Research-Related"],
["Other" ,"Other"]
]),
{label: "Mechanism:", value: "SBIR/STTR"} // 'value' sets the default
)mech_award_avg = {
// Filter by years
const filtered = myf_mechanism_data_filtered.filter(d =>
[2021, 2022, 2023, 2024].includes(d.Original_Year) &&
d.Mechanism == mechanism
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
// Summarize (Mean) and Round
const mean = latestEntries.reduce((sum, d) => sum + d.Cum_Outyear, 0) / latestEntries.length;
return Math.round(mean);
}
formatted_mech_award_avg = mech_award_avg >= 1e9
? usdFormatter.format(mech_award_avg / 1e9) + "B"
: usdFormatter.format(mech_award_avg / 1e6) + "M";
// 2. Cumulative 2025 values
mech_award_2025 = {
// Filter by years
const filtered = myf_mechanism_data_filtered.filter(d =>
[2025].includes(d.Original_Year) &&
d.Mechanism == mechanism
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
return latestEntries[0].Cum_Outyear;
}
formatted_mech_award_2025 = mech_award_2025 >= 1e9
? usdFormatter.format(mech_award_2025 / 1e9) + "B"
: usdFormatter.format(mech_award_2025 / 1e6) + "M";
// 3. Cumulative 2026 Values
mech_cum_award_2026 = {
// Filter by years
const filtered = myf_mechanism_data_filtered.filter(d =>
[2026].includes(d.Original_Year) &&
d.Mechanism == mechanism
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
return latestEntries[0]?.Cum_Outyear ?? 0;
}
formatted_mech_cum_award_2026 = mech_cum_award_2026 >= 1e9
? usdFormatter.format(mech_cum_award_2026 / 1e9) + "B"
: usdFormatter.format(mech_cum_award_2026 / 1e6) + "M";Plot.plot({
marginLeft: 50,
width: 928,
// Add labels to the axes here
x: {
label: "Award Date",
tickFormat: "%b", // Shows 'Jan', 'Feb', etc. Use "%B" for full names
},
y: {
label: "Obligations",
tickFormat: d => {
if (Math.abs(d) >= 1e9) {
return `$${(d / 1e9).toFixed(1)}B`; // Billions
} else {
return `$${(d / 1e6).toFixed(1)}M`; // Millions
}
},
grid: true // Optional: makes it easier to read values
},
// specify colors here for outstanding lines
color: {
domain: [2021, 2022, 2023, 2024, 2025, 2026], // The specific values in your data
range: ["#6605a6", "#0b72b3", "#2A9D8F", "#e96ab6", "#F4A261", "#E63946"] // The specific colors you want (e.g., Red and Blue)
},
marks: [
// Filter for years that ARE NOT 2025 or 2026 (as an example)
Plot.lineY(myf_mechanism_data_filtered.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Outyear",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
myf_mechanism_data_filtered,
Plot.pointerX(Plot.binX({}, { x: "NoA_date", thresholds: 1000, insetTop: 20 }))
),
// The Current Day line
Plot.ruleX([todayNormalized], {
stroke: "black",
strokeDasharray: "4", // 4px dash, 4px gap
strokeOpacity: 1 // Makes it look a bit more subtle
}),
Plot.text([todayNormalized], {
x: d => d,
text: d => "Today",
textAnchor: "start",
fontSize: 20,
dx: 10,
dy: -50,
fill: "black"
}),
//Tooltip code
Plot.tip(
myf_mechanism_data_filtered,
Plot.pointerX(
Plot.binX(
{title: (v) => v},
{
x: "NoA_date",
thresholds: 1000,
render(index, scales, values, dimensions, context) {
const g = d3.select(context.ownerSVGElement).append("g");
const [i] = index;
if (i !== undefined) {
const myf_mechanism_data_filtered = values.title[i];
g.attr(
"transform",
`translate(${Math.min(
values.x1[i],
dimensions.width - dimensions.marginRight - 200
)}, 20)`
).append(() =>
Plot.plot({
marginTop: 20,
height: 150,
width: 150,
axis: null,
y: {domain: scales.scales.color.domain},
marks: [
Plot.frame({ fill: "white", stroke: "currentColor" }),
Plot.dot(myf_mechanism_data_filtered, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(myf_mechanism_data_filtered, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(myf_mechanism_data_filtered, {
y: "Original_Year",
text: d => new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
notation: 'compact',
compactDisplay: 'short'
}).format(d.Cum_Outyear),
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(myf_mechanism_data_filtered, {
frameAnchor: "top-left",
dy: -20,
text: (d) => {
// 1. Convert the string "2026-03-30" into a JS Date object
const dateObj = new Date(d.NoA_date);
// 2. Format it to "Month Day" (e.g., "March 30")
return dateObj.toLocaleString("en-US", {
month: "long",
day: "numeric"
});
},
fontSize: 16,
fontWeight: "bold"
})
]
})
);
}
return g.node();
}
}
)
)
),
Plot.ruleY([0])
]
})Plot.plot({
marginLeft: 50,
width: 928,
// Add labels to the axes here
x: {
label: "Award Date",
tickFormat: "%b", // Shows 'Jan', 'Feb', etc. Use "%B" for full names
},
y: {
label: "Number of Awards",
grid: true // Optional: makes it easier to read values
},
// specify colors here for outstanding lines
color: {
domain: [2021, 2022, 2023, 2024, 2025, 2026], // The specific values in your data
range: ["#6605a6", "#0b72b3", "#2A9D8F", "#e96ab6", "#F4A261", "#E63946"] // The specific colors you want (e.g., Red and Blue)
},
marks: [
// Filter for years that ARE NOT 2025 or 2026 (as an example)
Plot.lineY(myf_mechanism_data_filtered.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Outyear_Share",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
myf_mechanism_data_filtered,
Plot.pointerX(Plot.binX({}, { x: "NoA_date", thresholds: 1000, insetTop: 20 }))
),
// The Current Day line
Plot.ruleX([todayNormalized], {
stroke: "black",
strokeDasharray: "4", // 4px dash, 4px gap
strokeOpacity: 1 // Makes it look a bit more subtle
}),
Plot.text([todayNormalized], {
x: d => d,
text: d => "Today",
textAnchor: "start",
fontSize: 20,
dx: 10,
dy: -50,
fill: "black"
}),
//Tooltip code
Plot.tip(
myf_mechanism_data_filtered,
Plot.pointerX(
Plot.binX(
{title: (v) => v},
{
x: "NoA_date",
thresholds: 1000,
render(index, scales, values, dimensions, context) {
const g = d3.select(context.ownerSVGElement).append("g");
const [i] = index;
if (i !== undefined) {
const myf_mechanism_data_filtered = values.title[i];
g.attr(
"transform",
`translate(${Math.min(
values.x1[i],
dimensions.width - dimensions.marginRight - 200
)}, 20)`
).append(() =>
Plot.plot({
marginTop: 20,
height: 150,
width: 150,
axis: null,
y: {domain: scales.scales.color.domain},
marks: [
Plot.frame({ fill: "white", stroke: "currentColor" }),
Plot.dot(myf_mechanism_data_filtered, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(myf_mechanism_data_filtered, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(myf_mechanism_data_filtered, {
y: "Original_Year",
text: "Cum_Outyear_Share",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(myf_mechanism_data_filtered, {
frameAnchor: "top-left",
dy: -20,
text: (d) => {
// 1. Convert the string "2026-03-30" into a JS Date object
const dateObj = new Date(d.NoA_date);
// 2. Format it to "Month Day" (e.g., "March 30")
return dateObj.toLocaleString("en-US", {
month: "long",
day: "numeric"
});
},
fontSize: 16,
fontWeight: "bold"
})
]
})
);
}
return g.node();
}
}
)
)
),
Plot.ruleY([0])
]
})These visualizations demonstrate that there is a difference in the number of awards and amount of funding obligated in FY 2025 to 2026 when filtering by IC.
mech_label = {
mechanism; // This "listens" for changes to the dropdown
const select = viewof mechanism.querySelector("select");
return select.options[select.selectedIndex].text;
}For example, based on your selection of Awards for the grant mechanism, the following totals are determined:
- The total funding obligated through multiyear funding mechanisms was more in FY 2025, which was
- This is compared to the average amount obligated through multiyear funding across FY 2021 through 2024, which was
- So far, the cumulative multiyear funding in FY 2026 is
To compare these totals with a different category, select a different “Mechanism” from the drop-down button.
By Grant Activity (General)
Use the following drop-down to compare FY trends in award types based on NIH Grant Activity
viewof activity = Inputs.select(
new Map([
["Research (R)" ,"R"],
["Training (T)" ,"T"],
["Fellowships (F)" ,"F"],
["Career Development (K)" ,"K"],
["Centers (P)" ,"P"],
["Cooperative Agreements (U)" ,"U"],
["International Research Training (D)" ,"D"]
]),
{label: "Activity:", value: "R"} // 'value' sets the default
)act_award_avg = {
// Filter by years
const filtered = myf_activity_data_filtered.filter(d =>
[2021, 2022, 2023, 2024].includes(d.Original_Year) &&
d.Activity == activity
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
// Summarize (Mean) and Round
const mean = latestEntries.reduce((sum, d) => sum + d.Cum_Outyear, 0) / latestEntries.length;
return Math.round(mean);
}
formatted_act_award_avg = act_award_avg >= 1e9
? usdFormatter.format(act_award_avg / 1e9) + "B"
: usdFormatter.format(act_award_avg / 1e6) + "M";
// 2. Cumulative 2025 values
act_award_2025 = {
// Filter by years
const filtered = myf_activity_data_filtered.filter(d =>
[2025].includes(d.Original_Year) &&
d.Activity == activity
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
return latestEntries[0].Cum_Outyear;
}
formatted_act_award_2025 = act_award_2025 >= 1e9
? usdFormatter.format(act_award_2025 / 1e9) + "B"
: usdFormatter.format(act_award_2025 / 1e6) + "M";
// 3. Cumulative 2026 Values
act_cum_award_2026 = {
// Filter by years
const filtered = myf_activity_data_filtered.filter(d =>
[2026].includes(d.Original_Year) &&
d.Activity == activity
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
return latestEntries[0]?.Cum_Outyear ?? 0;
}
formatted_act_cum_award_2026 = act_cum_award_2026 >= 1e9
? usdFormatter.format(act_cum_award_2026 / 1e9) + "B"
: usdFormatter.format(act_cum_award_2026 / 1e6) + "M";Plot.plot({
marginLeft: 50,
width: 928,
// Add labels to the axes here
x: {
label: "Award Date",
tickFormat: "%b", // Shows 'Jan', 'Feb', etc. Use "%B" for full names
},
y: {
label: "Obligations",
tickFormat: d => {
if (Math.abs(d) >= 1e9) {
return `$${(d / 1e9).toFixed(1)}B`; // Billions
} else {
return `$${(d / 1e6).toFixed(1)}M`; // Millions
}
},
grid: true // Optional: makes it easier to read values
},
// specify colors here for outstanding lines
color: {
domain: [2021, 2022, 2023, 2024, 2025, 2026], // The specific values in your data
range: ["#6605a6", "#0b72b3", "#2A9D8F", "#e96ab6", "#F4A261", "#E63946"] // The specific colors you want (e.g., Red and Blue)
},
marks: [
// Filter for years that ARE NOT 2025 or 2026 (as an example)
Plot.lineY(myf_activity_data_filtered.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Outyear",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
myf_activity_data_filtered,
Plot.pointerX(Plot.binX({}, { x: "NoA_date", thresholds: 1000, insetTop: 20 }))
),
// The Current Day line
Plot.ruleX([todayNormalized], {
stroke: "black",
strokeDasharray: "4", // 4px dash, 4px gap
strokeOpacity: 1 // Makes it look a bit more subtle
}),
Plot.text([todayNormalized], {
x: d => d,
text: d => "Today",
textAnchor: "start",
fontSize: 20,
dx: 10,
dy: -50,
fill: "black"
}),
//Tooltip code
Plot.tip(
myf_activity_data_filtered,
Plot.pointerX(
Plot.binX(
{title: (v) => v},
{
x: "NoA_date",
thresholds: 1000,
render(index, scales, values, dimensions, context) {
const g = d3.select(context.ownerSVGElement).append("g");
const [i] = index;
if (i !== undefined) {
const myf_activity_data_filtered = values.title[i];
g.attr(
"transform",
`translate(${Math.min(
values.x1[i],
dimensions.width - dimensions.marginRight - 200
)}, 20)`
).append(() =>
Plot.plot({
marginTop: 20,
height: 150,
width: 150,
axis: null,
y: {domain: scales.scales.color.domain},
marks: [
Plot.frame({ fill: "white", stroke: "currentColor" }),
Plot.dot(myf_activity_data_filtered, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(myf_activity_data_filtered, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(myf_activity_data_filtered, {
y: "Original_Year",
text: d => new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
notation: 'compact',
compactDisplay: 'short'
}).format(d.Cum_Outyear),
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(myf_activity_data_filtered, {
frameAnchor: "top-left",
dy: -20,
text: (d) => {
// 1. Convert the string "2026-03-30" into a JS Date object
const dateObj = new Date(d.NoA_date);
// 2. Format it to "Month Day" (e.g., "March 30")
return dateObj.toLocaleString("en-US", {
month: "long",
day: "numeric"
});
},
fontSize: 16,
fontWeight: "bold"
})
]
})
);
}
return g.node();
}
}
)
)
),
Plot.ruleY([0])
]
})Plot.plot({
marginLeft: 50,
width: 928,
// Add labels to the axes here
x: {
label: "Award Date",
tickFormat: "%b", // Shows 'Jan', 'Feb', etc. Use "%B" for full names
},
y: {
label: "Number of Awards",
grid: true // Optional: makes it easier to read values
},
// specify colors here for outstanding lines
color: {
domain: [2021, 2022, 2023, 2024, 2025, 2026], // The specific values in your data
range: ["#6605a6", "#0b72b3", "#2A9D8F", "#e96ab6", "#F4A261", "#E63946"] // The specific colors you want (e.g., Red and Blue)
},
marks: [
// Filter for years that ARE NOT 2025 or 2026 (as an example)
Plot.lineY(myf_activity_data_filtered.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Outyear_Share",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
myf_activity_data_filtered,
Plot.pointerX(Plot.binX({}, { x: "NoA_date", thresholds: 1000, insetTop: 20 }))
),
// The Current Day line
Plot.ruleX([todayNormalized], {
stroke: "black",
strokeDasharray: "4", // 4px dash, 4px gap
strokeOpacity: 1 // Makes it look a bit more subtle
}),
Plot.text([todayNormalized], {
x: d => d,
text: d => "Today",
textAnchor: "start",
fontSize: 20,
dx: 10,
dy: -50,
fill: "black"
}),
//Tooltip code
Plot.tip(
myf_activity_data_filtered,
Plot.pointerX(
Plot.binX(
{title: (v) => v},
{
x: "NoA_date",
thresholds: 1000,
render(index, scales, values, dimensions, context) {
const g = d3.select(context.ownerSVGElement).append("g");
const [i] = index;
if (i !== undefined) {
const myf_activity_data_filtered = values.title[i];
g.attr(
"transform",
`translate(${Math.min(
values.x1[i],
dimensions.width - dimensions.marginRight - 200
)}, 20)`
).append(() =>
Plot.plot({
marginTop: 20,
height: 150,
width: 150,
axis: null,
y: {domain: scales.scales.color.domain},
marks: [
Plot.frame({ fill: "white", stroke: "currentColor" }),
Plot.dot(myf_activity_data_filtered, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(myf_activity_data_filtered, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(myf_activity_data_filtered, {
y: "Original_Year",
text: "Cum_Outyear_Share",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(myf_activity_data_filtered, {
frameAnchor: "top-left",
dy: -20,
text: (d) => {
// 1. Convert the string "2026-03-30" into a JS Date object
const dateObj = new Date(d.NoA_date);
// 2. Format it to "Month Day" (e.g., "March 30")
return dateObj.toLocaleString("en-US", {
month: "long",
day: "numeric"
});
},
fontSize: 16,
fontWeight: "bold"
})
]
})
);
}
return g.node();
}
}
)
)
),
Plot.ruleY([0])
]
})These visualizations demonstrate that there is a difference in the number of awards and amount of funding obligated in FY 2025 to 2026 when filtering by IC.
act_label = {
activity; // This "listens" for changes to the dropdown
const select = viewof activity.querySelector("select");
return select.options[select.selectedIndex].text;
}For example, based on your selection of , the following totals are determined:
- The total funding obligated through multiyear funding mechanisms was more in FY 2025, which was
- This is compared to the average amount obligated through multiyear funding across FY 2021 through 2024, which was
- So far, the cumulative multiyear funding in FY 2026 is grants have been awarded
To compare these totals with a different category, select a different “Activity” different “Activity” from the drop-down button.
By Grant Activity (Specific)
Use the following drop-down to compare FY trends in award types based on NIH Grant Activity
viewof specific = Inputs.select(
new Map([
["RF1" ,"RF1"],
["R01" ,"R01"],
["R35" ,"R35"]
]),
{label: "Activity Code:", value: "RF1"} // 'value' sets the default
)specific_act_award_avg = {
// Filter by years
const filtered = myf_specific_activity_data_filtered.filter(d =>
[2021, 2022, 2023, 2024].includes(d.Original_Year) &&
d.Activity == specific
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
// Summarize (Mean) and Round
const mean = latestEntries.reduce((sum, d) => sum + d.Cum_Outyear, 0) / latestEntries.length;
return Math.round(mean);
}
formatted_specific_act_award_avg = specific_act_award_avg >= 1e9
? usdFormatter.format(specific_act_award_avg / 1e9) + "B"
: usdFormatter.format(specific_act_award_avg / 1e6) + "M";
// 2. Cumulative 2025 values
specific_act_award_2025 = {
// Filter by years
const filtered = myf_specific_activity_data_filtered.filter(d =>
[2025].includes(d.Original_Year) &&
d.Activity == specific
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
return latestEntries[0].Cum_Outyear;
}
formatted_specific_act_award_2025 = specific_act_award_2025 >= 1e9
? usdFormatter.format(specific_act_award_2025 / 1e9) + "B"
: usdFormatter.format(specific_act_award_2025 / 1e6) + "M";
// 3. Cumulative 2026 Values
specific_act_cum_award_2026 = {
// Filter by years
const filtered = myf_specific_activity_data_filtered.filter(d =>
[2026].includes(d.Original_Year) &&
d.Activity == specific
);
// Group & Slice Max (Get the latest entry for each year)
// Reduce the array into a Map to keep only the entry with the max NoA_date per year
const latestEntries = Array.from(
filtered.reduce((acc, curr) => {
const existing = acc.get(curr.Original_Year);
if (!existing || new Date(curr.NoA_date) > new Date(existing.NoA_date)) {
acc.set(curr.Original_Year, curr);
}
return acc;
}, new Map()).values()
);
return latestEntries[0]?.Cum_Outyear ?? 0;
}
formatted_specific_act_cum_award_2026 = specific_act_cum_award_2026 >= 1e9
? usdFormatter.format(specific_act_cum_award_2026 / 1e9) + "B"
: usdFormatter.format(specific_act_cum_award_2026 / 1e6) + "M";Plot.plot({
marginLeft: 50,
width: 928,
// Add labels to the axes here
x: {
label: "Award Date",
tickFormat: "%b", // Shows 'Jan', 'Feb', etc. Use "%B" for full names
},
y: {
label: "Obligations",
tickFormat: d => {
if (Math.abs(d) >= 1e9) {
return `$${(d / 1e9).toFixed(1)}B`; // Billions
} else {
return `$${(d / 1e6).toFixed(1)}M`; // Millions
}
},
grid: true // Optional: makes it easier to read values
},
// specify colors here for outstanding lines
color: {
domain: [2021, 2022, 2023, 2024, 2025, 2026], // The specific values in your data
range: ["#6605a6", "#0b72b3", "#2A9D8F", "#e96ab6", "#F4A261", "#E63946"] // The specific colors you want (e.g., Red and Blue)
},
marks: [
// Filter for years that ARE NOT 2025 or 2026 (as an example)
Plot.lineY(myf_specific_activity_data_filtered.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Outyear",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
myf_specific_activity_data_filtered,
Plot.pointerX(Plot.binX({}, { x: "NoA_date", thresholds: 1000, insetTop: 20 }))
),
// The Current Day line
Plot.ruleX([todayNormalized], {
stroke: "black",
strokeDasharray: "4", // 4px dash, 4px gap
strokeOpacity: 1 // Makes it look a bit more subtle
}),
Plot.text([todayNormalized], {
x: d => d,
text: d => "Today",
textAnchor: "start",
fontSize: 20,
dx: 10,
dy: -50,
fill: "black"
}),
//Tooltip code
Plot.tip(
myf_specific_activity_data_filtered,
Plot.pointerX(
Plot.binX(
{title: (v) => v},
{
x: "NoA_date",
thresholds: 1000,
render(index, scales, values, dimensions, context) {
const g = d3.select(context.ownerSVGElement).append("g");
const [i] = index;
if (i !== undefined) {
const myf_specific_activity_data_filtered = values.title[i];
g.attr(
"transform",
`translate(${Math.min(
values.x1[i],
dimensions.width - dimensions.marginRight - 200
)}, 20)`
).append(() =>
Plot.plot({
marginTop: 20,
height: 150,
width: 150,
axis: null,
y: {domain: scales.scales.color.domain},
marks: [
Plot.frame({ fill: "white", stroke: "currentColor" }),
Plot.dot(myf_specific_activity_data_filtered, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(myf_specific_activity_data_filtered, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(myf_specific_activity_data_filtered, {
y: "Original_Year",
text: d => new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
notation: 'compact',
compactDisplay: 'short'
}).format(d.Cum_Outyear),
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(myf_specific_activity_data_filtered, {
frameAnchor: "top-left",
dy: -20,
text: (d) => {
// 1. Convert the string "2026-03-30" into a JS Date object
const dateObj = new Date(d.NoA_date);
// 2. Format it to "Month Day" (e.g., "March 30")
return dateObj.toLocaleString("en-US", {
month: "long",
day: "numeric"
});
},
fontSize: 16,
fontWeight: "bold"
})
]
})
);
}
return g.node();
}
}
)
)
),
Plot.ruleY([0])
]
})Plot.plot({
marginLeft: 50,
width: 928,
// Add labels to the axes here
x: {
label: "Award Date",
tickFormat: "%b", // Shows 'Jan', 'Feb', etc. Use "%B" for full names
},
y: {
label: "Number of Awards",
grid: true // Optional: makes it easier to read values
},
// specify colors here for outstanding lines
color: {
domain: [2021, 2022, 2023, 2024, 2025, 2026], // The specific values in your data
range: ["#6605a6", "#0b72b3", "#2A9D8F", "#e96ab6", "#F4A261", "#E63946"] // The specific colors you want (e.g., Red and Blue)
},
marks: [
// Filter for years that ARE NOT 2025 or 2026 (as an example)
Plot.lineY(myf_specific_activity_data_filtered.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Outyear_Share",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
myf_specific_activity_data_filtered,
Plot.pointerX(Plot.binX({}, { x: "NoA_date", thresholds: 1000, insetTop: 20 }))
),
// The Current Day line
Plot.ruleX([todayNormalized], {
stroke: "black",
strokeDasharray: "4", // 4px dash, 4px gap
strokeOpacity: 1 // Makes it look a bit more subtle
}),
Plot.text([todayNormalized], {
x: d => d,
text: d => "Today",
textAnchor: "start",
fontSize: 20,
dx: 10,
dy: -50,
fill: "black"
}),
//Tooltip code
Plot.tip(
myf_specific_activity_data_filtered,
Plot.pointerX(
Plot.binX(
{title: (v) => v},
{
x: "NoA_date",
thresholds: 1000,
render(index, scales, values, dimensions, context) {
const g = d3.select(context.ownerSVGElement).append("g");
const [i] = index;
if (i !== undefined) {
const myf_specific_activity_data_filtered = values.title[i];
g.attr(
"transform",
`translate(${Math.min(
values.x1[i],
dimensions.width - dimensions.marginRight - 200
)}, 20)`
).append(() =>
Plot.plot({
marginTop: 20,
height: 150,
width: 150,
axis: null,
y: {domain: scales.scales.color.domain},
marks: [
Plot.frame({ fill: "white", stroke: "currentColor" }),
Plot.dot(myf_specific_activity_data_filtered, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(myf_specific_activity_data_filtered, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(myf_specific_activity_data_filtered, {
y: "Original_Year",
text: "Cum_Outyear_Share",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(myf_specific_activity_data_filtered, {
frameAnchor: "top-left",
dy: -20,
text: (d) => {
// 1. Convert the string "2026-03-30" into a JS Date object
const dateObj = new Date(d.NoA_date);
// 2. Format it to "Month Day" (e.g., "March 30")
return dateObj.toLocaleString("en-US", {
month: "long",
day: "numeric"
});
},
fontSize: 16,
fontWeight: "bold"
})
]
})
);
}
return g.node();
}
}
)
)
),
Plot.ruleY([0])
]
})These visualizations demonstrate that there is a difference in the number of awards and amount of funding obligated in FY 2025 to 2026 when filtering by IC.
For example, based on your selection of , the following totals are determined:
- The total funding obligated through multiyear funding mechanisms was more in FY 2025, which was
- This is compared to the average amount obligated through multiyear funding across FY 2021 through 2024, which was
- So far, the cumulative multiyear funding in FY 2026 is
To compare these totals with a different category, select a different “Activity Code” radio button.