Public
Edited
May 6
Insert cell
Insert cell
vis1 = Plot.plot({
// style: { background: "#eee" },
// subtitle: "Скільки коштів і на що виділяв USAID у 2024 році",
x: { domain: [0, 100] },
y: { domain: [0, 100] },
height: width > 650 ? 500 : 420,
width: width > 650 ? 650 : 350,
axis: null,
marginRight: 100,
marks: [
// Plot.ruleY([0]),
// Plot.lineY(aapl, { x: "Date", y: "Close" }),
Plot.arrow([0], {
x1: 90,
y1: 40,
x2: 83,
y2: 13,
dx: 0,
stroke: "black",
opacity: width > 650 ? 1 : 0.1,
bend: 30,
inset: 1,
strokeOpacity: 0.7,
strokeWidth: 1
}),
Plot.text(
[
width > 650
? "Напрямки Освіта та соціальні послуги ($13,1 млн) та Мир та безпека ($12,9 млн)"
: ""
],
{
x: 84,
y: 50,
textAnchor: "start",
lineWidth: 9,
fontSize: 14,
lineHeight: 1.2
// fontWeight: 300
}
),
() =>
svg`<g transform="translate(-5,0)">${treemapchart(
data0,
width > 650 ? 6 : 5,
2021
)}`
],
caption: html`<small style='color:grey;text-align: start;'>Дані: foreignassistance.gov</small>`
})
Insert cell
data = (await FileAttachment("usaid@2.tsv").tsv()).map((d) => {
return {
...d,
date_start: d3.timeParse("%d.%m.%y")(d["Початок реалізації"]),
date_end: d3.timeParse("%d.%m.%y")(d["Кінець"]),
bud: (+d["Бюджет проекту"].replace(/\s+/g, "") / 1000000).toFixed(0),
amount: d3.scaleSqrt()(+d["Бюджет проекту"].replace(/\s+/g, ""))
};
})
Insert cell
data0 = [
{ name: "import", size: null },
{ name: "import.Пряма бюджетна допомога", size: "3899.0" },
{ name: "import.Економічний розвиток", size: "1054.0" },
{ name: "import.Гуманітарна допомога", size: "580.4" },
{ name: "import.Демократія права людини та урядування", size: "340.5" },
{ name: "import.Охорона здоров'я", size: "76.8" },
{ name: "import.Програмна підтримка", size: "76.4" },
{ name: "import.Освіта та соціальні послуги", size: "13.1" },
{ name: "import.Мир та безпека", size: "12.9" }
].map((d) => {
return { ...d, size: +d.size };
})
Insert cell
// treemapchart(data0, width > 650 ? 6.5 : 4, 2021)
Insert cell
vis2 = Plot.plot({
style: { fontSize: 14 },
width: width > 650 ? 700 : 350,
height: width > 650 ? 1100 : 950,
marginTop: 70,
y: { grid: true },
// grid: true
marginLeft: width > 650 ? 311 : 10,
marks: [
Plot.ruleX([new Date("2025-01-24")], { strokeDasharray: "2 2", dy: -20 }),
Plot.link(data, {
x1: "date_start",
x2: "date_end",
y: "Навзва проекту",
opacity: 0.7,
tip: width > 650 ? true : true,
title: (d) => `Проєкт: ${d["Навзва проекту"]}\nСума: $${d.bud} млн`,
stroke: "#dd1c77",
strokeWidth: (d) => d.amount / 1150,
sort: { y: "-x1" }
}),
Plot.text(
data.filter((d) => d.bud > 50),
{
// frameAnchor: "top-left",
textAnchor: "end",
lineWidth: 12,
fontSize: 11,
fontWeight: 300,
x: (d) =>
d["Навзва проекту"] !=
"Децентралізація приносить кращі результати та ефективність (DOBRE)" &&
d["Навзва проекту"] !=
`Програма сприяння громадській активності "Долучайся!"`
? d.date_start
: width > 650
? new Date("2027-06-01")
: new Date("2027-09-01"),
y: "Навзва проекту",
dx: -3,
stroke: "white",
// strokeWidth: 6,
fill: "#000",
text: (d) => "$" + d.bud + " млн"
}
),
Plot.arrow([0], {
x1: new Date("2020-04-01"),
x2: new Date("2023-05-15"),
y1: "Демократичне врядування у Східній Україні",
y2: "Трансформація комунікацій"
// bend: true
}),
Plot.text(["Рішення про призупинення фінансування\n↓"], {
frameAnchor: "top",
lineWidth: 14,
x: new Date("2025-01-24"),
dy: -60,
stroke: "white",
strokeWidth: 4,
fill: "#000",
fontWeight: 600
}),

Plot.text(["↑\nНовіші проекти\nза датою початку"], {
frameAnchor: "top",
lineWidth: 14,
// fontSize: 16,
fontWeight: 600,
x: new Date("2017-05-24"),
dy: -65,
dx: width > 650 ? -140 : 0,
stroke: "white",
opacity: width > 650 ? 1 : 0,
strokeWidth: 4,
fill: "#000"
}),
Plot.text(
[
`Проєкт SPARC на $439 млн продовжує попередній проєкт "Енергетична безпека" (див. графіки внизу). За ці гроші, зокрема, ремонтують пошкоджену росіянами енергетичну інфраструктуру і виконують оголошений минулого року Зеленським плану розпобудови 1 ГВт маневрової газової генерації.`
],
{
frameAnchor: width > 650 ? "top-left" : "top-left",
lineWidth: width > 650 ? 12 : 11,
fontSize: 14,
lineHeight: 1.2,
// fontWeight: 300,
x: width > 650 ? new Date("2016-09-01") : new Date("2016-05-01"),
dy: width > 650 ? 20 : 25,
dx: -5,
stroke: "white",
strokeWidth: 4,
fill: "#000"
}
),
Plot.text([width > 650 ? `→` : `→`], {
frameAnchor: "top-right",
fontSize: 16,
fontWeight: 300,
x: new Date("2022-05-01"),
dy: width > 650 ? 73 : 55,
dx: width > 650 ? 0 : -5,
stroke: "white",
strokeWidth: 4,
fill: "#000"
}),
Plot.axisX({ frameAnchor: "top", inset: 20, ticks: 6 }),
Plot.axisY({
lineWidth: 28,
fontSize: 12,
tickSize: 0,
opacity: width > 650 ? 1 : 0,
label: null,
fontWeight: 300,
lineHeight: 0.9
})
],
caption: html`<small style='color:grey;text-align: start;'>Наведіть, щоб побачити деталі ${iconHandTap}.<br>
Джерело: Кабінет міністрів</small>`
})
Insert cell
import {
franklinLight,
iconArrowCursor,
iconHandTap
} from "@shadfriguii/fonts-and-icons"
Insert cell
function treemapchart(data_, mult, year) {
// Stratify.
const data = d3.stratify().path((d) => d.name.replace(/\./g, "/"))(data_);

const width = d3.scaleSqrt()(d3.sum(data_, (d) => d.size)) * mult;
const height = d3.scaleSqrt()(d3.sum(data_, (d) => d.size)) * mult;

// Compute the layout.
const root = d3
.treemap()
.tile(d3.treemapBinary) // e.g., d3.treemapSquarify -- d3.treemapBinary
.size([width, height])
.padding(5)
.round(true)(
d3
.hierarchy(data)
.sum((d) => d.data.size)
.sort((a, b) => b.value - a.value)
);

// Create the SVG container.
const svg = d3
.create("svg")
.attr("viewBox", [0, -10, width, height + 10])
.attr("width", width)
.attr("height", height)
.attr(
"style",
"max-width: 100%; height: auto; font: 10px sans-serif;"
// background:#eee
);

// Add a cell for each leaf of the hierarchy, with a link to the corresponding GitHub page.
const leaf = svg
.selectAll("g")
.data(root.leaves())
.join("a")
.attr("transform", (d) => `translate(${d.x0},${d.y0})`);

// console.log("!", root.leaves());

// Append a tooltip.
const format = d3.format(".3r");

leaf
.append("title")
.text(
(d) =>
`${d.data.id.slice(1).replace(/.+\//g, "")}\n${
"$" + format(d.value / 1000) + " млрд"
}`
);

// Append a color rectangle.
leaf
.append("rect")
.attr("id", (d) => (d.leafUid = DOM.uid("leaf")).id)
.attr("fill", (d) =>
d.data.id == "/import/Приховані статті" ? "red" : "#dd1c77"
)
.attr("fill-opacity", 0.6)
.attr("width", (d) => d.x1 - d.x0)
.attr("height", (d) => d.y1 - d.y0);

// Append a clipPath to ensure text does not overflow.
leaf
.append("clipPath")
.attr("id", (d) => (d.clipUid = DOM.uid("clip")).id)
.append("use")
.attr("xlink:href", (d) => d.leafUid.href);

// Append multiline text. The last line shows the value and has a specific formatting.
leaf
.append("text")
.attr("clip-path", (d) => d.clipUid)
.selectAll("tspan")
.data((d) =>
d.data.id
.split("/")
.at(-1)
.split(/(?=[A-Z][a-z])|\s+/g)
.concat(format(d.value))
)
.join("tspan")
.attr("x", 3)
.attr("text-anchor", "start")
// .attr("opacity", (d, i, nodes) => console.log(nodes))
.attr(
"y",
(d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`
)
.attr("fill-opacity", (d, i, nodes) =>
i === nodes.length - 1 ? 0.7 : null
)
.attr("font-size", 13)
.text((d, i, nodes) =>
i === nodes.length - 1 ? "$" + d / 1000 + " млрд" : d
);

return Object.assign(svg.node());
}

Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more