data_1 = {
const response = await fetch("https://files.grant-witness.us/exporter_overview_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
data_obligate = {
const response = await fetch("https://files.grant-witness.us/exporter_overview_breakdown_obligate.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}Grantmaking over Time for NIH
The following interactive funding curves are adapted from Jeremy Berg. These visualizations demonstrate differences in the cumulative number of awards and cumulative amount obligated across NIH from fiscal year (FY) 2021 to 2026.
For more discussions about these funding curves, read the related articles published by Nature and Science.
For more information about how we created these graphs, see our Methods section.
data = {
if(grant_type == "All"){
const response = await fetch("https://files.grant-witness.us/exporter_IC_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
if(grant_type == "New & Competitive Renewal"){
const response = await fetch("https://files.grant-witness.us/exporter_IC_newcomp_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
if(grant_type == "Non-Competitive Renewal"){
const response = await fetch("https://files.grant-witness.us/exporter_IC_noncomp_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
}
data_2 = data.filter(function(grant) {
return grant.IC === inst_centers;
});data_3 = {
const response = await fetch("https://files.grant-witness.us/exporter_new_comp_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}data_4 = {
const response = await fetch("https://files.grant-witness.us/exporter_noncomp_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}data_5 = {
if(grant_type2 == "All"){
const response = await fetch("https://files.grant-witness.us/exporter_mechanism_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
if(grant_type2 == "New & Competitive Renewal"){
const response = await fetch("https://files.grant-witness.us/exporter_mechanism_newcomp_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
if(grant_type2 == "Non-Competitive Renewal"){
const response = await fetch("https://files.grant-witness.us/exporter_mechanism_noncomp_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
}
data_6 = data_5.filter(function(grant) {
return grant.Mechanism === mechanism;
});data_7 = {
if(grant_type3 == "All"){
const response = await fetch("https://files.grant-witness.us/exporter_activity_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
if(grant_type3 == "New & Competitive Renewal"){
const response = await fetch("https://files.grant-witness.us/exporter_activity_newcomp_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
if(grant_type3 == "Non-Competitive Renewal"){
const response = await fetch("https://files.grant-witness.us/exporter_activity_noncomp_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
}
data_8 = data_7.filter(function(grant) {
return grant.Activity === activity;
});data_9 = {
if(grant_type4 == "All"){
const response = await fetch("https://files.grant-witness.us/nih_state_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
if(grant_type4 == "New & Competitive Renewal"){
const response = await fetch("https://files.grant-witness.us/nih_state_newcomp_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
if(grant_type4 == "Non-Competitive Renewal"){
const response = await fetch("https://files.grant-witness.us/nih_state_noncomp_breakdown.csv");
const text = await response.text();
return d3.csvParse(text, d3.autoType);
}
}
data_10 = data_9.filter(function(grant) {
return grant.State === state;
});// 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_award_avg = {
// Filter by years
const filtered = data_1.filter(d =>
[2021, 2022, 2023, 2024].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()
);
// Summarize (Mean) and Round
const mean = latestEntries.reduce((sum, d) => sum + d.Cum_Award, 0) / latestEntries.length;
return Math.round(mean);
}
cum_cost_avg = {
// 1. Filter by years
const filtered = data_1.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_Cost, 0) / latestEntries.length;
return Math.round(mean);
}
formatted_cum_cost_avg = usdFormatter.format(cum_cost_avg / 1e9) + "B"
// 2. Cumulative 2025 values
cum_award_2025 = {
// Filter by years
const filtered = data_1.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_Award;
}
cum_cost_2025 = {
// Filter by years
const filtered = data_1.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_Cost;
}
formatted_cum_cost_2025 = usdFormatter.format(cum_cost_2025 / 1e9) + "B"
// 3. Cumulative 2026 Values
last_cum_award_2026 = {
// Filter by years
const filtered = data_1.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_Award;
}
last_cum_cost_2026 = {
// Filter by years
const filtered = data_1.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_Cost;
}
formatted_last_cum_cost_2026 = usdFormatter.format(last_cum_cost_2026 / 1e9) + "B"newcomp_award_avg = {
// Filter by years
const filtered = data_3.filter(d =>
[2021, 2022, 2023, 2024].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()
);
// Summarize (Mean) and Round
const mean = latestEntries.reduce((sum, d) => sum + d.Cum_Award, 0) / latestEntries.length;
return Math.round(mean);
}
// 2. Cumulative 2025 values
newcomp_award_2025 = {
// Filter by years
const filtered = data_3.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_Award;
}
// 3. Cumulative 2026 Values
newcomp_cum_award_2026 = {
// Filter by years
const filtered = data_3.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_Award;
}noncomp_award_avg = {
// Filter by years
const filtered = data_4.filter(d =>
[2021, 2022, 2023, 2024].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()
);
// Summarize (Mean) and Round
const mean = latestEntries.reduce((sum, d) => sum + d.Cum_Award, 0) / latestEntries.length;
return Math.round(mean);
}
// 2. Cumulative 2025 values
noncomp_award_2025 = {
// Filter by years
const filtered = data_4.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_Award;
}
// 3. Cumulative 2026 Values
noncomp_cum_award_2026 = {
// Filter by years
const filtered = data_4.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_Award;
}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: "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(data_1.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Award",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_1,
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(
data_1,
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 data_1 = 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(data_1, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_1, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_1, {
y: "Original_Year",
text: "Cum_Award",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_1, {
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: "Fraction 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(data_1.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Award_Norm",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_1,
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(
data_1,
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 data_1 = 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(data_1, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_1, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_1, {
y: "Original_Year",
text: "Cum_Award_Norm",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text([data_1[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])
]
})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(data_obligate.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Cost",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_obligate,
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(
data_obligate,
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 data_obligate = 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(data_obligate, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_obligate, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_obligate, {
y: "Original_Year",
text: d => {
if (d.Cum_Cost >= 1000000000) {
// Format as Billions if 1,000,000,000 or greater
return `$${(d.Cum_Cost / 1000000000).toFixed(1)}B`;
} else {
// Format as Millions otherwise
return `$${(d.Cum_Cost / 1000000).toFixed(1)}M`;
}
},
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_obligate, {
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 cumulative number of awards graphs is restricted to Type 1 (new) and Type 5 (non-competing continuation) grants, while the cumulative obligations graph includes all grant Types (1-9), to be consistent with the method that NIH RePORTER chose to display funding curves. More information about the different types of NIH grants can be found here.
From these graphs, we can see that there was a significant lag in FY 2025 funding, which occurred during the spring and into the summer (March through August). This is consistent with reported disruptions to grant review and funding processes that occurred in 2025.
- Specifically, the total number of awards made in FY 2025 is .
- This total number is less than the average number of awards across FY 2021 - 2024, which was .
- Similarly, the total funding obligated across these awards was less in FY 2025, which was .
- This is compared to the average amount obligated across FY 2021 through 2024, which was .
- Additionally, we are seeing a lag in awards made in FY 2026 so far, with only awards.
- The cumulative cost from these obligations in FY 2026 is .
By Award Type
The following graphs visualize funding across FY based on award types, comparing new and competitive renewal awards (Type 1, 2, 4, 9) with non-competitive renewal awards (Type 5, 6, 7, 8). More information about the different types of NIH grants can be found here.
- New applications are the first request for funding for a project that has not previously received that specific grant.
- Competitive Renewals are requests for continued funding after the original project period ends; these applications go through full peer review and must compete again for funding.
- In contrast, Non-Competitive Renewals provide continued funding for the next budget period within an already approved multi-year project and generally do not undergo full peer review, assuming satisfactory progress and compliance with administrative requirements.
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(data_3.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Award",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_3,
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(
data_3,
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 data_3 = 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(data_3, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_3, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_3, {
y: "Original_Year",
text: "Cum_Award",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_3, {
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(data_4.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Award",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_4,
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(
data_4,
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 data_4 = 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(data_4, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_4, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_4, {
y: "Original_Year",
text: "Cum_Award",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_4, {
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])
]
})- In FY 2025, the total number of New and Competitively Renewed awards (n = ) was lower than previous years.
- The average annual total in the previous four FYs was awards.
- So far, in FY 2026, New and Competitive Renewal awards have been made.
- Similar trends are observed for Non-Competitive Renewals, where by the end of FY 2025, awards had been made.
- This is compared with an average year-end total of awards across FY 2021–2024.
- Currently in FY 2026, only Non-Competitive Renewal Awards have been made so far.
By State
Use the following drop-down to compare FY trends in award types based on the state that the NIH Grant was awarded
viewof grant_type4 = Inputs.radio(
new Map([
["All" ,"All"],
["New & Competitive Renewal" ,"New & Competitive Renewal"],
["Non-Competitive Renewal" ,"Non-Competitive Renewal"]
]),
{label: "Award Type:", value: "All"} // 'value' sets the default
)
viewof state = Inputs.select(
new Map([
<!-- ["Alberta (Canada)" ,"AB"], -->
["Alaska" ,"AK"],
["Arkansas" ,"AR"],
<!-- ["American Samoa (Territory)" ,"AS"], -->
["Arizona" ,"AZ"],
<!-- ["British Columbia (Canada)" ,"BC"], -->
["California" ,"CA"],
["Colorado", "CO"],
["Connecticut", "CT"],
["District of Columbia", "DC"],
["Delaware", "DE"],
["Florida", "FL"],
["Georgia", "GA"],
<!-- ["Guam (Territory)", "GU"], -->
["Hawaii", "HI"],
["Iowa", "IA"],
["Idaho", "ID"],
["Illinois", "IL"],
["Indiana", "IN"],
["Kansas", "KS"],
["Kentucky", "KY"],
["Louisiana", "LA"],
["Massachusetts", "MA"],
["Maryland", "MD"],
["Maine", "ME"],
["Michigan", "MI"],
["Minnesota", "MN"],
["Missouri", "MO"],
["Mississippi", "MS"],
<!-- ["Manitoba (Canada)", "MB"], -->
["Montana", "MT"],
["North Carolina", "NC"],
["North Dakota", "ND"],
["Nebraska", "NE"],
["New Hampshire", "NH"],
["New Jersey", "NJ"],
["New Mexico", "NM"],
["Nevada", "NV"],
["New York", "NY"],
<!-- ["Nova Scotia (Canada)", "NS"], -->
["Ohio", "OH"],
["Oklahoma", "OK"],
<!-- ["Ontario (Canada)", "ON"], -->
["Oregon", "OR"],
["Pennsylvania", "PA"],
<!-- ["Province of Quebec", "PQ"], -->
["Puerto Rico", "PR"],
<!-- ["Quebec", "QC"], -->
["Rhode Island", "RI"],
["South Carolina", "SC"],
["South Dakota", "SD"],
<!-- ["Saskatchewan (Canada)", "SK"], -->
["Tennessee", "TN"],
["Texas", "TX"],
["Utah", "UT"],
["Virginia", "VA"],
<!-- ["Virgin Islands (Territory)", "VI"], -->
["Vermont", "VT"],
["Washington", "WA"],
["Wisconsin", "WI"],
["West Virginia", "WV"],
["Wyoming", "WY"],
]),
{label: "State:", value: "AK"} // 'value' sets the default
)state_award_avg = {
// Filter by years
const filtered = data_10.filter(d =>
[2021, 2022, 2023, 2024].includes(d.Original_Year) &&
d.State == state
);
// 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_Award, 0) / latestEntries.length;
return Math.round(mean);
}
// 2. Cumulative 2025 values
state_award_2025 = {
// Filter by years
const filtered = data_10.filter(d =>
[2025].includes(d.Original_Year) &&
d.State == state
);
// 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_Award;
}
// 3. Cumulative 2026 Values
state_cum_award_2026 = {
// Filter by years
const filtered = data_10.filter(d =>
[2026].includes(d.Original_Year) &&
d.State == state
);
// 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_Award ?? "there is no data available about how many";
}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(data_10.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Award",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_10,
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(
data_10,
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 data_10 = 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(data_10, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_10, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_10, {
y: "Original_Year",
text: "Cum_Award",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_10, {
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: "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(data_10.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Cost",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_10,
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(
data_10,
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 data_10 = 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(data_10, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_10, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_10, {
y: "Original_Year",
text: d => new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
notation: 'compact',
compactDisplay: 'short'
}).format(d.Cum_Cost),
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_10, {
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 State.
state_label = {
state; // This "listens" for changes to the dropdown
const select = viewof state.querySelector("select");
return select.options[select.selectedIndex].text;
}For example, based on your selection of Awards in , the following totals are determined:
- In FY 2025, only grants had been awarded.
- The average annual total in the previous four FYs was
- So far, in FY 2026, grants have been awarded
To compare these totals with a different category, select a different “Award Type” radio button and/or a different “State” from the drop-down button.
By Institutes and Centers (ICs)
Use the following drop-down to compare FY trends in award types based on NIH Institute or Center.
viewof grant_type = Inputs.radio(
new Map([
["All" ,"All"],
["New & Competitive Renewal" ,"New & Competitive Renewal"],
["Non-Competitive Renewal" ,"Non-Competitive Renewal"]
]),
{label: "Award Type:", value: "All"} // 'value' sets the default
)
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 = {
// Filter by years
const filtered = data_2.filter(d =>
[2021, 2022, 2023, 2024].includes(d.Original_Year) &&
d.IC == inst_centers
);
// 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_Award, 0) / latestEntries.length;
return Math.round(mean);
}
// 2. Cumulative 2025 values
inst_award_2025 = {
// Filter by years
const filtered = data_2.filter(d =>
[2025].includes(d.Original_Year) &&
d.IC == inst_centers
);
// 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_Award;
}
// 3. Cumulative 2026 Values
inst_cum_award_2026 = {
// Filter by years
const filtered = data_2.filter(d =>
[2026].includes(d.Original_Year) &&
d.IC == inst_centers
);
// 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_Award ?? "there is no data available about how many";
}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(data_2.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Award",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_2,
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(
data_2,
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 data_2 = 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(data_2, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_2, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_2, {
y: "Original_Year",
text: "Cum_Award",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_2, {
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: "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(data_2.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Cost",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_2,
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(
data_2,
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 data_2 = 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(data_2, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_2, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_2, {
y: "Original_Year",
text: d => new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
notation: 'compact',
compactDisplay: 'short'
}).format(d.Cum_Cost),
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_2, {
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:
- In FY 2025, only grants had been awarded.
- The average annual total in the previous four FYs was
- So far, in FY 2026, grants have been awarded.
To compare these totals with a different category, select a different “Award Type” radio button and/or 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 grant_type2 = Inputs.radio(
new Map([
["All" ,"All"],
["New & Competitive Renewal" ,"New & Competitive Renewal"],
["Non-Competitive Renewal" ,"Non-Competitive Renewal"]
]),
{label: "Award Type:", value: "All"} // 'value' sets the default
)
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 = data_6.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_Award, 0) / latestEntries.length;
return Math.round(mean);
}
// 2. Cumulative 2025 values
mech_award_2025 = {
// Filter by years
const filtered = data_6.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_Award;
}
// 3. Cumulative 2026 Values
mech_cum_award_2026 = {
// Filter by years
const filtered = data_6.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_Award ?? "there is no data available about how many";
}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(data_6.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Award",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_6,
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(
data_6,
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 data_6 = 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(data_6, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_6, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_6, {
y: "Original_Year",
text: "Cum_Award",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_6, {
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: "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(data_6.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Cost",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_6,
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(
data_6,
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 data_6 = 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(data_6, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_6, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_6, {
y: "Original_Year",
text: d => new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
notation: 'compact',
compactDisplay: 'short'
}).format(d.Cum_Cost),
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_6, {
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 Mechanism.
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:
- In FY 2025, only grants have been awarded
- The average annual total in the previous four FYs was
- So far, in FY 2026, grants have been awarded.
To compare these totals with a different category, select a different “Award Type” radio button and/or a different “Mechanism” from the drop-down button.
By Grant Activity
Use the following drop-down to compare FY trends in award types based on NIH Grant Activity
viewof grant_type3 = Inputs.radio(
new Map([
["All" ,"All"],
["New & Competitive Renewal" ,"New & Competitive Renewal"],
["Non-Competitive Renewal" ,"Non-Competitive Renewal"]
]),
{label: "Award Type:", value: "All"} // 'value' sets the default
)
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 = data_8.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_Award, 0) / latestEntries.length;
return Math.round(mean);
}
// 2. Cumulative 2025 values
act_award_2025 = {
// Filter by years
const filtered = data_8.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_Award;
}
// 3. Cumulative 2026 Values
act_cum_award_2026 = {
// Filter by years
const filtered = data_8.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_Award ?? "there is no data available about how many";
}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(data_8.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Award",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_8,
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(
data_8,
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 data_8 = 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(data_8, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_8, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_8, {
y: "Original_Year",
text: "Cum_Award",
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_8, {
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: "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(data_8.filter(d => ![2015, 2016, 2017, 2018, 2019, 2020].includes(d.Original_Year)), {
x: d => new Date(d.NoA_date),
y: "Cum_Cost",
stroke: "Original_Year", // This will now use the color scale
strokeWidth: 3 // Optional: make them pop a bit more
}),
Plot.ruleX(
data_8,
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(
data_8,
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 data_8 = 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(data_8, {
y: "Original_Year",
fill: (d) => scales.color(d.Original_Year),
r: 5,
frameAnchor: "left",
symbol: "square2",
dx: 6
}),
Plot.text(data_8, {
y: "Original_Year",
text: (d) => `FY ${d.Original_Year} :`,
fontSize: 16,
frameAnchor: "left",
dx: 13
}),
Plot.text(data_8, {
y: "Original_Year",
text: d => new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
notation: 'compact',
compactDisplay: 'short'
}).format(d.Cum_Cost),
fontSize: 16,
frameAnchor: "right",
dx: -8
}),
Plot.text(data_8, {
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 Grant Activity.
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 Awards for the grant activity, the following totals are determined:
- In FY 2025, only grants had been awarded
- The average annual total in the previous four FYs was
- So far, in FY 2026, grants have been awarded.
To compare these totals with a different category, select a different “Award Type” radio button and/or a different “Activity” from the drop-down button.