Published
Edited
Aug 8, 2021
Insert cell
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
// viewof dd3 = {
// const dd3 = Inputs.select({
// title: "How many $'s are spent on each recipiant from:",
// options: [
// { label: "Your taxes (Average Federal Tax: $10,644)", value: 10644 },
// { label: "Actual Budget (Total Obligations): 9 Trillion", value: 8992408699023}
// ],
// value: 10644
// });
// return dd3;
// }


viewof dd3 = Inputs.text({label: "Your tax bill: ", placeholder: "Enter your tax bill", value: 10000})
Insert cell
viewof groupByPriority = Inputs.radio(
new Map([["Step 1: Congress funds Federal Accounts", "Level1"], ["Step 2: Federal Accounts are owned by Federal Agencies", "Level2"], ["Step 3: Some Agencies distribute funds to UNICEF and WFP", "Level3"]]), {label: "Select a new step to change the visualization below", value: "Level1"})
Insert cell
viewof step = Player( [ "1", "2", "3"], {
delay: 10000,
autoplay: true,
loop: false,
show: { play: true }
})
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, 250])
.range([6, 16])
.clamp(true)

const svg = d3.create("svg")
.attr("viewBox", `-${width / 2} -${height /(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", "9px")
.style("border", "1px solid #DDD")
.style("font-size", "0.8rem")
.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){
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/>"))


var formattedString = String(d.data.award_description.join("<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(`
<b>Award Recipiant</b> : ${d.data.name} <br>
<b>Your taxes provide</b> : ${formated_money} <br>
<b> First Sentence in Wikipedia</b> : ${formattedString}<br>`);
}


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 *.9 / v[2];

view = v;

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) {
const focus0 = focus;
if (d.height ==0)
{
focus = d.parent}
else {focus = d}

const transition = svg.transition()
.duration(1500)
.tween("zoom", d => {
const i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2]);
return t => zoomTo(i(t));
});

}


var variable = {"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 (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 Funds Provided to Recipients in 2020", "starting_opacity": 0}
console.log(variable)


const 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 v = [root.x, root.y, root.r * 2]
var view = v
let focus = root;
let format = d3.format(",d")
let current_circle = undefined;
var k = 1
var node = svg.append("g")
.selectAll("circle")
.data(root.descendants().slice(1), d => d.data.key)

var label = svg.append("g")
.selectAll("text")
.data(root.descendants().slice(1), d => d.data.key)

const t = svg.transition()
.duration(1500);

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(2000)
.attr('opacity', d => opacity_true ? .6 : (d.data.un_money == "yes" ) ? .2 : .6)
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))
.style("fill", font_color)
.style("fill-opacity", function(d) { if (fontsize(d.r * k) > 6 && d.depth == 1) {return 1} else {return 1} })
.text(function (d) {
var formated_money =parseFloat(d.data.outlay)*parseFloat(dd3)/8992408699023
formated_money = formatter.format(formated_money)

return d.data.name + ": " + formated_money ;
})
)
label
.transition()
.attr('opacity', d => opacity_true ? 1 : (d.data.un_money == "yes" ) ? 1 : 0)

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")})
// tooltip + styling

var second_title = svg.append("text")
.attr("x", 0)
.attr("y", -height/2 + 30)
.attr("text-anchor", "middle")
.style("font-size", "16px")
second_title.text(head_title)
}
return Object.assign(svg.node(), { setYear });
}
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("recipents@10.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 = 500
Insert cell
height = width
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