Published unlisted
Edited
May 11, 2022
Insert cell
# Final Project
Insert cell
# The Ethical Cost of our Diets
Insert cell
data = FileAttachment("Daily U.S. Impact on Lives and Days of Suffering - All Vertebrates (1).csv").csv()
Insert cell
//https://stackoverflow.com/questions/33510625/trim-white-spaces-in-both-object-key-and-value-recursively
dataTrimmed = JSON.parse(JSON.stringify(data).replace(/"\s+|\s+"/g,'"')).map((d) => ({...d, 'total.suffering.US.daily': numSuf(d)}))
Insert cell
function numSuf(data) {
return parseInt(data['total.suffering.US.daily'].replace(/,/g, ''))
}
Insert cell
function numKg(data, type) {
if (data['animal.prod.code'] !== type) return 0;
return parseInt(data['total.kg'].replace(/,/g, '')) / usPop
}
Insert cell
blocksWidth = 800
Insert cell
width = 1050
Insert cell
blocksHeight = 500
Insert cell
height = graphicFormat === "Standard size" ? blocksHeight + 480 : standardRepresentationHeight + 480
Insert cell
viewof category = Inputs.select(["all", ...d3.group(dataTrimmed, d=>d["animal.prod.code"]).keys()], {value: "all", label: "Food Category"})
Insert cell
viewof scope = Inputs.radio(["United States", "Me"], {value: "Me", label: "Data Scope"})
Insert cell
#### The below will only be used for your personal visualization (Data Scope: Me). The US averages will be used for any empty inputs.
Insert cell
viewof form = Inputs.form({
beef: Inputs.text({label: "My average daily beef consumption in kg (US avg: " + dataTrimmed.reduce((partialSum, d) => partialSum + numKg(d, "beef"), 0).toLocaleString() + ")"}),
chicken: Inputs.text({label: "My average daily chicken consumption in kg (US avg: " + dataTrimmed.reduce((partialSum, d) => partialSum + numKg(d, "chicken"), 0).toLocaleString() + ")"}),
dairy: Inputs.text({label: "My average daily dairy consumption in kg (US avg: " + dataTrimmed.reduce((partialSum, d) => partialSum + numKg(d, "dairy"), 0).toLocaleString() + ")"}),
egg: Inputs.text({label: "My average daily egg consumption in kg (US avg: " + dataTrimmed.reduce((partialSum, d) => partialSum + numKg(d, "egg"), 0).toLocaleString() + ")"}),
fish: Inputs.text({label: "My average daily fish consumption in kg (US avg: " + dataTrimmed.reduce((partialSum, d) => partialSum + numKg(d, "fish"), 0).toLocaleString() + ")"}),
pork: Inputs.text({label: "My average daily pork consumption in kg (US avg: " + dataTrimmed.reduce((partialSum, d) => partialSum + numKg(d, "pork"), 0).toLocaleString() + ")"}),
turkey: Inputs.text({label: "My average daily turkey consumption in kg (US avg: " + dataTrimmed.reduce((partialSum, d) => partialSum + numKg(d, "turkey"), 0).toLocaleString() + ")"})
})
Insert cell
viewof graphicFormat = Inputs.radio(["Standard size", "Standard box representation"], {label: "Graphic format", value: "Standard box representation"})
Insert cell
<div id="viz_container">
<svg id="svg_container" width=${width} height=${height}>
<text x=0 y=30 class="label" style="font-size: 39px; fill: black">How much animal suffering can I prevent?</text>
<text x=0 y=60 class="label" id="subtitle" style="font-size: 16px; fill: black">A box is over 1,643,780 days of suffering, each day in the US</text>
</svg>
</div>
Insert cell
### The Data
The full dataset used for this graphic, along with a description of the methodology that was used to produce it, is here: https://forum.effectivealtruism.org/posts/fR6GJEh5aYjGwYBWG/analysis-which-animal-products-cause-the-most-animal. "Days of suffering" is an estimate based on factors including the normal lifespan of farmed animals, direct and indirect mortality (the animals we eat directly, but also the animals that are expected to die before we eat them, and the animals used to feed the animals we eat), and an elasticity factor, which is used to adjust for price elasticity of supply and demand. The elasticity factor means that the graphic is showing how much suffering would be expected to be eliminated if someone were to stop purchasing a food product. (For example, if someone stops purchasing 10kg of beef each year, it might only reduce beef production by 5kg due to economic factors. The elasticity factor takes this into account, and depicts what our purchase of 10kg of beef actually causes, which is the suffering that goes into the production of 5kg.)

If you are looking for effective ways to donate to improve the lives of animals, a great resource is https://animalcharityevaluators.org/donation-advice/recommended-charities.
Insert cell
<style>
.label {
font-family: 'Monaco', Monospace;
fill: #fff9d8;
font-size: 12px;
}
.legend {
font-family: 'Monaco', Monospace;
font-size: 14px;
}
</style>
Insert cell
svg = d3.select(viz_container).select("#svg_container")
Insert cell
baseMultiplier = 10
Insert cell
usPop = 334585863
Insert cell
total = dataFiltered.reduce((partialSum, d) => partialSum + d['total.suffering.US.daily'], 0);
Insert cell
sufScalar = (total / (blocksHeight * blocksWidth)) * (baseMultiplier * baseMultiplier * 4)
Insert cell
standardRep = scope === "Me" ? 1 : 100000
Insert cell
currentScalar = graphicFormat === "Standard size" ? sufScalar : standardRep
Insert cell
standardRepresentationHeight = (sufScalar / standardRep) * blocksHeight
Insert cell
function adjustData(d) {
const formNum = form[d['animal.prod.code']] ? form[d['animal.prod.code']] : dataTrimmed.reduce((partialSum, d) => partialSum + numKg(d, d['animal.prod.code']), 0)
return d['total.suffering.US.daily'] * 365 / usPop * (formNum/dataTrimmed.reduce((partialSum, d) => partialSum + numKg(d, d['animal.prod.code']), 0))
}
Insert cell
dataAdjusted = dataTrimmed.map(d => scope === "United States" ? d : ({...d, 'total.suffering.US.daily': adjustData(d)}))
Insert cell
dataFiltered = category === "all" ? dataAdjusted : dataAdjusted.filter(d => d['animal.prod.code'] === category)
Insert cell
sufferingBoxes = dataFiltered.flatMap(d => Array.from({ length: d['total.suffering.US.daily'] / (currentScalar) }, (_, idx) => d))
Insert cell
multiplier = baseMultiplier * 2
Insert cell
colorMap = [...new Set(dataFiltered.map(d => category === "all" ? d['animal.prod.code'] : d['format.code']))];
Insert cell
function getFill(data) {
if (category === "all") {
return d3.interpolateTurbo(colorMap.indexOf(data['animal.prod.code'])/colorMap.length)
} else {
return d3.interpolateTurbo(colorMap.indexOf(data['format.code'])/colorMap.length)
}
}
Insert cell
boxes = svg.selectAll(".sufferingBox")
.data(sufferingBoxes)
.join("rect")
.attr("x", (d, i) => (i * multiplier) % blocksWidth + 75)
.attr("y", (d, i) => Math.floor((i * multiplier) / blocksWidth) * multiplier + 100)
.attr("width", baseMultiplier)
.attr("height", baseMultiplier)
.attr("fill", d => getFill(d))
.attr("class", "sufferingBox")
Insert cell
svg.selectAll(".legend")
.data(colorMap)
.join("text")
.attr("class", "legend")
.attr("x", 15)
.attr("y", (d, i) => height - 360 + 16 * i)
.text(d => d)
Insert cell
svg.selectAll(".legendBox")
.data(colorMap)
.join("rect")
.attr("class", "legendBox")
.attr("x", 0)
.attr("y", (d, i) => height - 370 + 16 * i)
.attr("width", baseMultiplier)
.attr("height", baseMultiplier)
.attr("fill", d => d3.interpolateTurbo(colorMap.indexOf(d)/colorMap.length))
Insert cell
subtitleEndString = scope === "Me" ? ((currentScalar === 1 ? " day" : " days") + " of suffering, each year of my consumption") : " days of suffering, each day of US consumption"
Insert cell
subtitleNumString = scope === "Me" ? currentScalar.toLocaleString() : (currentScalar/365).toLocaleString() + " years or " + currentScalar.toLocaleString()
Insert cell
svg.select("#subtitle")
.text("A single box is at least " + subtitleNumString + subtitleEndString)
Insert cell
tooltipElement = svg
.append("rect")
.attr("id", "tooltipElement")
.attr("x", 50)
.attr("y", 50)
.attr("width", 160)
.attr("height", 47)
.style("visibility", "hidden")
.style("opacity", .8)
Insert cell
tooltipText = svg
.append("text")
.attr("x", 120)
.attr("y", 50)
.attr("class", "label")
.style("visibility", "hidden")
Insert cell
tooltipText2 = svg
.append("text")
.attr("x", 120)
.attr("y", 50)
.attr("class", "label")
.style("visibility", "hidden")
Insert cell
tooltipText3 = svg
.append("text")
.attr("x", 120)
.attr("y", 50)
.attr("class", "label")
.style("visibility", "hidden")
Insert cell
function format(text) {
if (text.length <= 22) {
return text;
} else {
return text.substring(0, 20) + "...";
}
}
Insert cell
addTooltip = (evt,d) => {
console.log(evt)
console.log(d)
let thisElem = evt.currentTarget;
d3.select(thisElem)
.style("stroke","black")
.style("stroke-width","2")
.raise();
d3.select('#tooltipElement')
.style("visibility", "visible")
.attr("x", evt.offsetX - 80)
.attr("y", evt.offsetY + 30)
.raise();
tooltipText
.style("visibility", "visible")
.attr("x", evt.offsetX - 78)
.attr("y", evt.offsetY + 43)
.html(d["animal.prod.code"])
.raise();
tooltipText2
.style("visibility", "visible")
.attr("x", evt.offsetX - 78)
.attr("y", evt.offsetY + 56)
.html(format(d["format.code"]))
.raise();
tooltipText3
.style("visibility", "visible")
.attr("x", evt.offsetX - 78)
.attr("y", evt.offsetY + 69)
.html(d["total.suffering.US.daily"].toLocaleString() + " days")
.raise();
}
Insert cell
removeTooltip = (evt) => {
d3.select(evt.currentTarget)
.style("stroke","")
.style("stroke-width","");
d3.select('#tooltipElement').style("visibility", "hidden")
tooltipText.style("visibility", "hidden")
tooltipText2.style("visibility", "hidden")
tooltipText3.style("visibility", "hidden")
}
Insert cell
{
boxes.on("mouseover", addTooltip);
boxes.on("mouseout", removeTooltip);
}
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more