Public
Edited
Oct 24, 2022
2 forks
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof rerunbutton = Inputs.button("Play short animation of this step")
Insert cell
chart = {
//root is the total data
var background_color = "#f2f3f4"
var unicef_color = "green"
var wfp_color = "blue"
var highlight_color = "#D79922"
var outline_color = "#4056A1"
var center_circle_color = "#C5CBE3"
var federal_color = "#f13C20"
var font_color = '#303C6C'

var fontsize = d3.scaleSqrt()
.domain([0, 150])
.range([1, 20])
.clamp(true)

const svg = d3.create("svg")
.attr("viewBox", `-${width / 2} -${height*1.15 /(2)} ${width} ${height}`)
.style("display", "block")
.style("background",background_color)
.style("cursor", "pointer")

// tooltip + styling
const toolTip = d3.select("body")
.append("div")
.attr("class", "toolTip")
.style("position", "absolute")
.style("display", "none")
.style("border-radius", "5px")
.style("height", "auto")
.style("background", "white")
.style("padding", "7px")
.style("border", "1px solid #DDD")
.style("font-size", "1.1rem")
.style("text-align", "left")


//https://stackoverflow.com/questions/149055/how-to-format-numbers-as-currency-strings
// Create our number formatter.
var formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',

// These options are needed to round to whole numbers if that's what you want.
//minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
// maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501)
});

// apple mouseovers
function leafMouseOver(event, d){
if (d.depth == 1) {
d3.select(event.target).attr("stroke-width", 4)
d3.select(event.target).attr("stroke", highlight_color)

d3.select(event.target)
var formated_money =parseFloat(d.data.outlay)*parseFloat(dd3)/8992408699023

var formated_money = formatter.format(formated_money)

toolTip.style("left", event.pageX + 18 + "px")
.style("top", event.pageY + 18 + "px")
.style("display", "block")
.html(`
Double click to zoom into this circle<br><br>
<b>${d.data.name}</b> <br>
Your taxes provide : <strong> ${formated_money} </strong>`)
}
else if (groupByPriority === "Level3") {
d3.select(event.target).attr("stroke-width", 4)
d3.select(event.target).attr("stroke", highlight_color)

var formattedString = String(d.data.award_description.join(",<br/><br/>-"))
var formated_money =parseFloat(d.data.outlay)*parseFloat(dd3)/8992408699023

formated_money = formatter.format(formated_money)

toolTip.style("left", event.pageX + 18 + "px")
.style("top", event.pageY + 18 + "px")
.style("display", "block")
.html(`
Double click to zoom into this circle<br><br>
<b>Award Recipiant</b> : ${d.data.name} <br>
<b>Your taxes provide</b> : ${formated_money} <br>
<b> Top award descriptions</b> : ${formattedString}<br><br>
<b>Part of:</b> ${d.parent.data.name}`);
}

else {
d3.select(event.target).attr("stroke-width", 4)
d3.select(event.target).attr("stroke", highlight_color)

var formated_money =parseFloat(d.data.outlay)*parseFloat(dd3)/8992408699023

formated_money = formatter.format(formated_money)

toolTip.style("left", event.pageX + 18 + "px")
.style("top", event.pageY + 18 + "px")
.style("display", "block")
.html(`
Double click to zoom into this circle<br><br>
<b> ${d.data.name}</b> <br>
<b> Your taxes provide :</b> <strong> ${formated_money} </strong> <br><br>
<b>Part of:</b> ${d.parent.data.name}`);
}

}

function leafMouseOut(event, d){
d3.select(event.target).attr("stroke", d => d.data.column_name == "federal_account_name" ? federal_color: (d.data.name == "International Affairs" || d.data.name == "Agency For International Development") ? highlight_color : outline_color)
d3.select(event.target).attr("stroke-width",d =>(d.data.name == "International Affairs" || d.data.name == "Agency for International Development") ? 5 : d => d.data.un_money == "no"? 5: 1)
toolTip.style("display", "none");
}
function setYear(level) {
function zoomTo(v) {
toolTip.style("display", "none");
k = width / v[2];


node
.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`)
.attr("r", d => d.r * k);
label.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`)
.attr("font-size", d => fontsize(d.r * k))
.style("fill-opacity", function(d) { if (fontsize(d.r * k) > 6 && d.parent == focus) {return 1} else {return 0} })
.text(function (d) {
return d.data.name.substring(0, 20) + "...";
})

}

function zoom(d) {
console.log(d)

if (d.height ==0)
{
focus = d.parent}
else {focus = d}
zoomTo([focus.x, focus.y, focus.r * 2])
if (d.parent )
{ if (d.children) {
zoom_title.text(d.data.name.substring(0, 30) + "...")}
else {zoom_title.text(d.parent.data.name.substring(0, 30) + "...")}}
else {zoom_title.text("")}
}



var variable = (level =="Level1" && step == "1") ?
{"data": account_function, "opacity_true": true, "opacity_name": "International Affairs", "zoom_in": false, "zoom_name": "International Affairs", "text_title1": "The budget passed by Congress assigns funds to Federal Accounts", "text_title2": "Most of the humanitarian aid falls under the budget function 'International Affairs'", "text_title3": " ", "text_title1_color": true, "text_title2_color": false, "text_title3_color": false, "head_title": "All Budget Functions and Federal Accounts", "starting_opacity": 0}
: (level =="Level1" && step == "2") ?
{"data": account_function, "opacity_true": true, "opacity_name": "International Affairs", "zoom_in": true, "zoom_name": "International Affairs", "text_title1": "The budget passed by Congress assigns funds to Federal Accounts", "text_title2": "Most of the humanitarian aid falls under the budget function 'International Affairs'", "text_title3": " ", "text_title1_color": false, "text_title2_color": true, "text_title3_color": false, "head_title": "All Budget Functions and Federal Accounts", "starting_opacity": .8}
: (level =="Level1" && step == "3") ?
{"data": account_function, "opacity_true": true, "opacity_name": "International Affairs", "zoom_in": false, "zoom_name": "International Affairs", "text_title1": "The budget passed by Congress assigns funds to Federal Accounts", "text_title2": "Most of the humanitarian aid falls under the budget function 'International Affairs'", "text_title3": " ", "text_title1_color": false, "text_title2_color": true, "text_title3_color": true, "head_title": "All Budget Functions and Federal Accounts", "starting_opacity": .8}
:(level =="Level2" && step == "1") ?
{"data": account_agency, "opacity_true": true, "opacity_name": "Agency For International Development", "zoom_in": false, "zoom_name": "Agency For International Development", "text_title1": "Federal Accounts are controlled by one or more Federal Agency", "text_title2": "The Agency For International Development controls many of the accounts for humanitarian aid", "text_title3": " ", "text_title1_color": true, "text_title2_color": false, "text_title3_color": false, "text_title": "Federal Accounts are controlled by one or more Federal Agency", "head_title": "All Federal Agencies and Federal Accounts", "starting_opacity": 0}
: (level =="Level2" && step == "2") ?
{"data": account_agency, "opacity_true": true, "opacity_name": "Agency For International Development", "zoom_in": true, "zoom_name": "Agency For International Development", "text_title1": "Federal Accounts are controlled by one or more Federal Agency", "text_title2": "The Agency For International Development controls many of the accounts for humanitarian aid", "text_title3": " ", "text_title1_color": false, "text_title2_color": true, "text_title3_color": false, "head_title": "All Federal Agencies and Federal Accounts", "starting_opacity": .8}
: (level =="Level2" && step == "3") ?
{"data": account_agency, "opacity_true": true, "opacity_name": "Agency for International Development", "zoom_in": false, "zoom_name": "Agency for International Development", "text_title1": "Federal Accounts are controlled by one or more Federal Agency", "text_title2": "The Agency For International Development controls many of the accounts for humanitarian aid", "text_title3": " ", "text_title1_color": false, "text_title2_color": true, "text_title3_color": true, "head_title": "All Federal Agencies and Federal Accounts", "starting_opacity": .8}
:(level =="Level3" && step == "1") ?
{"data": account_recipients, "opacity_true": true, "opacity_name": "Agency for International Development", "zoom_in": false, "zoom_name": "Agency for International Development", "text_title1": "The agencies (biggest circle) decide how to allocate funds to recipients", "text_title2": "These Federal Accounts (bold orange circles) are a small faction of the total Federal Accounts", "text_title3": "Double click the smallest circle to see recipients. WFP is in blue, UNICEF is green", "text_title1_color": true, "text_title2_color": false, "text_title3_color": false, "head_title": "Federal Accounts that give 1 cent of average tax bill to UNICEF or WFP", "starting_opacity": 0}
: (level =="Level3" && step == "2") ?
{"data": account_agency, "opacity_true": false, "opacity_name": "Agency For International Development", "zoom_in": false, "zoom_name": "Agency For International Development", "text_title1": "The agencies (biggest circle) decide how to allocate funds to recipients", "text_title2": "These Federal Accounts ( bold orange circles) are a small faction of the total Federal Accounts", "text_title3": "Double click the smallest circle to see recipients. WFP is in blue, UNICEF is green", "text_title1_color": false, "text_title2_color": true, "text_title3_color": false, "head_title": "All Federal Agencies and Federal Accounts", "starting_opacity": 0}
:
{"data": account_recipients, "opacity_true": true, "opacity_name": "Agency for International Development", "zoom_in": false, "zoom_name": "International Disaster Assistance, Funds Appropriated To The President, Us Agency For International Development", "text_title1": "The agencies (biggest circle) decide how to allocate funds to recipients", "text_title2": "These Federal Accounts (bold orange circles) are a small faction of the total Federal Accounts", "text_title3": "Double click the smallest circle to see recipients. WFP is in blue, UNICEF is green", "text_title1_color": false, "text_title2_color": false, "text_title3_color": true, "head_title": "Federal Accounts that give 1 cent of average tax bill to UNICEF or WFP", "starting_opacity": .8}
var root = pack(variable.data)
var opacity_true = variable.opacity_true
var opacity_name = variable.opacity_name
var zoom_in = variable.zoom_in
var zoom_name = variable.zoom_name
var starting_opacity = variable.starting_opacity
var head_title = variable.head_title
var text_title1 = variable.text_title1
var text_title2 = variable.text_title2
var text_title3 = variable.text_title3
var text_title1_color = variable.text_title1_color
var text_title2_color = variable.text_title2_color
var text_title3_color = variable.text_title3_color
var focus = root
var v = [root.x, root.y, root.r * 2]
let format = d3.format(",d")
let current_circle = undefined;
var k = 1
var node = svg.append("g")
.selectAll("circle")
.data(root.descendants().slice(1))

var label = svg.append("g")
.selectAll("text")
.data(root.descendants().slice(1))
svg
.on("dblclick", (event) => zoom( root));
node = node
.join(
enter => enter.append('circle')
.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`)
.attr("r", d => d.r * k)
//if there are still children, color by how far in you are
.attr("stroke", d => d.data.column_name == "federal_account_name" ? highlight_color: (d.data.name == "International Affairs" || d.data.name == "Agency For International Development" ) ? highlight_color : outline_color)
.attr("stroke-width", d => (d.data.name == "International Affairs" || d.data.name == "Agency For International Development") ? 5 : 2)
.attr("fill", d => d.children ? background_color: d.data.name =="World Food Program" ? wfp_color: d.data.name =="Unicef" ? unicef_color: center_circle_color)
.attr("opacity", starting_opacity)
.attr("class", d=> d.data.name == zoom_name ? "pick_me": "never_mind")
.on("dblclick", (event, d) => focus !== d && (zoom(d), event.stopPropagation()))
.on("mouseover", (event, d) => leafMouseOver(event, d))
.on("mouseout", (event, d) => leafMouseOut(event, d)),
)
node
// .transition()
// .duration(1000)
.attr('opacity', d => opacity_true ? .7 : (d.data.un_money == "yes" ) ? .2 : 1)
label = label
.join(
enter => enter.append('text')
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.attr("transform", d => `translate(${(d.x - v[0]) * k},${(d.y - v[1]) * k})`)
.attr("opacity", starting_opacity)

//label resized using new radius
.attr("font-size", d => fontsize(d.r * k))
.attr('font-weight', 700)
.style("fill", font_color)
.style("fill-opacity", function(d) { if (fontsize(d.r * k) > 6 && d.depth == 1) {return 1} else {return 0} })
.text(function (d) {
return d.data.name.substring(0, 20) + "...";
})
)
label
.attr('opacity', d => opacity_true ? 1 : (d.data.un_money == "yes" ) ? 1 : 0)


// tooltip + styling

svg.append('rect')
.attr('x', -width / 2)
.attr('y', -height*1.15 /(2))
.attr('width', width)
.attr('height', height/9)
.attr('stroke', 'black')
.attr('fill', 'white');
var title1 = svg.append("text")
.attr("x", 0)
.attr("y", -height/2 -25)
.attr("text-anchor", "middle")
.style("font-size", "20px")
title1.text(text_title1)
.attr('font-weight', text_title1_color? 700: 500)
var title2 = svg.append("text")
.attr("x", 0)
.attr("y", -height/2)
.attr("text-anchor", "middle")
.style("font-size", "20px")
console.log(text_title1_color, text_title2_color, text_title3_color)
title2.text(text_title2)
.attr('font-weight', text_title2_color? 700: 500)
var title3 = svg.append("text")
.attr("x", 0)
.attr("y", -height/2 + 25)
.attr("text-anchor", "middle")
.style("font-size", "20px")
.style('fill', text_title3_color)
title3.text(text_title3)
.attr('font-weight', text_title3_color? 700: 500)
var first_title = svg.append("text")
.attr("x", 0)
.attr("y", -height/2 - 50)
.attr("text-anchor", "middle")
.style("font-size", "22px")
.style("text-decoration", "underline")
first_title.text(head_title)
var second_title = svg.append("text")
.attr("x", 0)
.attr("y", -height/2 + 60)
.attr("text-anchor", "middle")
.style("font-size", "20px")
second_title.text("Double click in circles to zoom in. Double click outside circles to zoom back out")
var second_title = svg.append("text")
.attr("x", 0)
.attr("y", -height/2 + 80)
.attr("text-anchor", "middle")
.style("font-size", "20px")
second_title.text("Hover for more information")
var zoom_title = svg.append("text")
.attr("x", 0)
.attr("y", -height/2 + 110)
.attr("text-anchor", "middle")
.style("font-size", "30px")
.attr('font-weight', 700)
d3.selectAll('circle.pick_me').attr("stroke", highlight_color)
d3.selectAll('circle.pick_me').filter(function(d, i) { return i == 0 }).select(function(d) {zoom_in ? zoom(d): console.log("nope")})
}
return Object.assign(svg.node(), { setYear });
}
Insert cell
//https://www.d3-graph-gallery.com/graph/custom_annotation.html


// Features of the annotation
annotations = [
{
note: {
label: "Double click green in circle to zoom in"
},
x: 220,
y: -370,
dy: -10,
dx: 10,
className: "Level2Initial"
},
{
note: {
label: "Hover/click to view more info"
},
x: 180,
y: -395,
dy: -30,
dx: 30,
className: "Level2Initial"
},
{
note: {
label: "Click outside circle to reset"
},
x: 400,
y: -450,
dy: 50,
dx: -100,
className: "Level1Zoom"
},
{
note: {
label: "Click outside circle to reset"
},
x: 400,
y: -450,
dy: 50,
dx: -100,
className: "Level2Zoom"
},
{
note: {
label: "Click outside circle to reset"
},
x: 400,
y: -450,
dy: 50,
dx: -100,
className: "Level3Zoom"
},
{
note: {
label: "Click outside circle to reset"
},
x: 400,
y: -450,
dy: 50,
dx: -100,
className: "Level4Zoom"
}
]


Insert cell
embed = html`
<style>
.wrapper {
position: relative;
}

#infoi {
position: absolute;
top: 0;
left: 0;

background: rgba(255, 255, 255, 0.75);
}
.close {
position: absolute;
top: 0;
right: 0;
}

.annotation rect.annotation-note-bg {
}

button {
background-color: #4CAF50; /* Green */
border: none;
color: white;
opacity: .6;
}

// .axis text {
// fill: var(--annotation-context-color);
// fill-opacity: 1;
// }

// .axis path {
// stroke-dasharray: 1,3;
// }



}

</style>
<div style="text-align:center;">
</div>

<div class="wrapper">
<div>
${viewof chart}
${viewof dd3}
</div>
<div id="infoi">
<button class="close" onclick="document.getElementById('infoi').style.display='none'" >Close</button>
<p class="text">
${viewof test}
</p>
</div>
</div>`

Insert cell
viewof test = groupByPriority === "Level1" ?
html`<p>Just an overview by budget -> budget subfunction -> agency -> accounts
</p>`
: groupByPriority === "Level2" ?
html`<p> First, most funds for <b><span style="color: blue">International Affairs</span></b>, include for WFP and UNICEF, is allocated with the Department of State, Foreign Operations, and Related Programs (SFOPS) bill </p>
<p> This bill authorizes and appropriates funds for federal accounts concerned with Internation Affairs </p>` :groupByPriority === "Level3" ?
html`<p> Each federal account is controlled by a federal agency. The federal agency <b><span style="color: blue">US Agency for International Development</span></b> controls many of the federal accounts that allocate money to UNICEF and WFP </p> ` :
html`<p> The federal agency uses the federal accounts to award funds to organizations. Only the federal account from which either <b><span style="color: green">WFP</span></b> or <b><span style="color: blue">UNICEF</span></b> recieved at least 0.1 cent of the average tax bill are shown here`
Insert cell
anim_chart = chart.setYear(groupByPriority)
Insert cell
run_on_change = {
groupByPriority
viewof step.update(0)
}
Insert cell
rerun = {
rerunbutton
viewof step.update(0);
viewof step.remoteClickPlayPause()
}
Insert cell

// viewof groupByPriority = Scrubber(
// [ "Level1", "Level2", "Level3", "Level4", "Level5"], {delay: 1000}, {"autoplay": false})
// // [ "Level1", "Level1"], {delay: 2000}, {"autoplay": false})
Insert cell
import {Scrubber} from "@mbostock/scrubber"
Insert cell
import {Player} from "@oscar6echo/player"

Insert cell
faStyle({ solid: true })
Insert cell

import {style as faStyle} from "@airbornemint/fontawesome"
Insert cell
account_function = FileAttachment("all_agencies_flare_budget_account@3.json").json()
Insert cell
account_agency = FileAttachment("all_agencies_flare_agency@3.json").json()
Insert cell
account_recipients = FileAttachment("specific_accounts_flare@24.json").json()
Insert cell
all = FileAttachment("all_agencies_flare_all_info@2.json").json()
Insert cell
import { callout } from "@d3/line-chart-with-tooltip"
Insert cell
pack = data => d3.pack()
.size([width, height])
.padding(3)
(d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value))
Insert cell
pack2 = data2 => d3.pack()
.size([width, height])
.padding(3)
(d3.hierarchy(data2)
.sum(d => d.value)
.sort((a, b) => b.value - a.value))
Insert cell
width = 932
Insert cell
height = width* 1.15
Insert cell
format = d3.format(",d")
Insert cell
import {slider, button, checkbox} from "@jashkenas/inputs"
Insert cell
color = d3.scaleLinear()
.domain([0, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl)
Insert cell
d3 = require("d3@6", "d3-svg-annotation@2")
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