<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title></title>
</head>
<body>
<h1>Manhattan's Coronavirus Trash Crash Continues</h1>
<h2>DSNY's May 2020 trash haul outside Manhattan increased an average of <span style="color: #cf7760;font-size:1.2em">3.6%</span>
from the same time last year, while Manhattan community
districts averaged a <span style="color:#5d537c;font-size:1.2em">18.86%</span> decrease.<br>
DSNY's June 2020 trash haul outside Manhattan increased an average of <span style="color: #cf7760;font-size:1.2em">15.22%</span>
from the same time last year, while Manhattan community
districts averaged a <span style="color:#5d537c;font-size:1.2em">6.55%</span> decrease.<br><br>
<a href="https://bl.ocks.org/ursulakaczmarek/raw/7f4d93b0944c7c7d29ef3cc6060b808d/"style="color:#5d537c;text-decoration:none">View year over year changes for all lockdown months here</a>
</h2>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.0/chroma.min.js"></script>
<div id="chart">
<svg width="1500" height="850"></svg>
</div>
<footer>
<p>data <a href="https://data.cityofnewyork.us/City-Government/DSNY-Monthly-Tonnage-Data/ebb7-mvp5"style="color:#5d537c;text-decoration:none">nyc open data</a></p>
<p>created by <a href="https://ursulakaczmarek.github.io/" style="color:#5d537c;text-decoration:none">ursula kaczmarek</a></p>
</footer>
</body>
</html>
body {
position: absolute;
font-family: "Montserrat", sans-serif;
}
h1,
h2 {
position: absolute;
left: 10px;
font-size: 1.3em;
font-weight: 100;
}
h2,
.label
{
top: 30px;
font-family: "Montserrat", sans-serif;
font-size: 0.9em;
font-weight: 100;
}
p,
.axis {
top: 30px;
font-family: "Montserrat", sans-serif;
font-size: 0.6em;
font-weight: 100;
}
.cd {
stroke: #ada7b3;
stroke-width: 0.5;
fill: none;
}
.tooltip {
color: grey;
position: absolute;
text-align: left;
width: 150px;
height: 55px;
padding: 2px;
font-family: "Montserrat", sans-serif;
font-size: 12px;
background: white;
}
.line {
fill: none;
stroke-width: 2;
}
.axis--x path {
display: none;
}
var cd_url = "https://raw.githubusercontent.com/ursulakaczmarek/open.trash.lab/master/dsny.tonnage/nyccd.geojson";
var trash_url = "https://data.cityofnewyork.us/resource/ebb7-mvp5.json?$where=month between '2019 / 06' and '2020 / 06'";
Promise.all([d3.json(cd_url), d3.json(trash_url)])
.then(function(bothSets) {
var districts = bothSets[0];
var tonnage = bothSets[1];
var trashData = tonnage.map(
row => [{
boroCD: row.borough_id.concat(row.communitydistrict),
date: new Date(row.month.split(" / ")[0] + "/" + row.month.split(" / ")[1] + "/01").toDateString().split(" ").slice(1).join(" "),
total: Object.entries(row).reduce(
(totals, type) => {
if (type[0].includes("tons"))
return totals + Number(type[1]);
else return +Number(totals).toFixed(3);
}, 0),
mgp: +Number(row.mgptonscollected).toFixed(3),
paper: +Number(row.papertonscollected).toFixed(3),
refuse: +Number(row.refusetonscollected).toFixed(3),
organics: +Number(row.resorganicstons).toFixed(3)
}]).reduce(
(a, b) => a.concat(b));
//June 2020 year over year values
var yoy = trashData.filter(
d => d.date.includes("Jun")).reduce(
(a, b) => {
function diff(a, b) {
return +Number(100 * ((a - b) / Math.abs(b))).toFixed(3);
}
var idx = a.findIndex(elem => elem.boroCD == b.boroCD);
if (~idx) {
a[idx].totalDiff = diff(b.total, a[idx].total);
a[idx].total2019 = a[idx].total;
a[idx].total2020 = b.total
} else {
a.push(JSON.parse(JSON.stringify(b)));
}
return a
}, []);
var mnmap = yoy.filter(d => d.boroCD.indexOf("1") === 0).map(d => d.totalDiff);
var othmap = yoy.filter(d => !d.boroCD.startsWith("1")).map(d => d.totalDiff);
const average = arr => arr.reduce( ( p, c ) => p + c, 0 ) / arr.length;
var mhav = average(mnmap), ohav = average(othmap);
console.log([mhav, ohav]);
var yoyMap = yoy.map(d => [d.boroCD, d.totalDiff, d.total2019, d.total2020]);
//top-level svg
var svg = d3.select("svg");
//line chart svg
var margin = { top: 80, right: 40, bottom: 30, left: 70},
width = 750 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
//map grouping element
var gMap = svg.append("svg")
.attr("transform", "translate(0, 0)");
//line chart grouping element
var g = svg.append("g")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//map parameters
var projection = d3.geoMercator()
.scale(Math.pow(2, 10.35 + 5.34))
.translate([1180 / 1.3, 800 / 1.2])
.center([-73.94, 40.50]);
//cd outlines
var path = d3.geoPath()
.projection(projection);
//cd choropleth
var maxChange = d3.max(yoyMap.map(d => d[1]));
var minChange = d3.min(yoyMap.map(d => d[1]));
var totalDomain = [minChange, maxChange]
var colorTotal = chroma.scale(["#5d537c", "#f28454"])
.domain(totalDomain);
//define the tooltip
var tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip");
//draw map outline
gMap.selectAll(".cd")
.data(districts.features)
.enter().append("path")
.attr("class", "cd")
.attr("d", path);
//draw choropleth
gMap.selectAll(".cd")
.data(yoyMap, d => (d[0] ? d[0] : d.properties.boro_cd))
.style("fill", d => colorTotal(d[1]))
.on("mouseover", (d, i) => {
tooltip.transition().duration(750).style("visibility", "visible")
tooltip.html("community district: " + d[0] + "<br/> " + "June 2019 tons: " + d[2] + "<br/> " + "June 2020 tons: " + d[3] + "<br/> " + "change: " + d[1] + "%")
tooltip.style("top", (d3.event.pageY + 1) + "px")
tooltip.style("left", (d3.event.pageX + 1) + "px")
})
.on("click", d => {
lines(trashData, d[0], g, d); })
.on("mouseout", (d, i) => {
tooltip.transition().duration(500).style("visibility", "hidden")
});
//map legend
gMap.append("text")
.attr("class", "label")
.attr("x", 855)
.attr("y", 105)
.text("% Change YoY June");
var defs = gMap.append("defs");
// append linearGradient element
var linearGradient = defs.append("linearGradient")
.attr("id", "linear-gradient");
linearGradient.selectAll("stop")
.data([{offset: "0%", color: "#5d537c"},{offset: "25%", color: "#875f73"},
{offset: "50%",color: "#ac6b6a"},{offset: "75%",color: "#cf7760"},
{offset: "100%",color: "#f28454"}])
.enter().append("stop")
.attr("offset", d => d.offset)
.attr("stop-color", d => d.color);
gMap.append("rect")
.attr("width", 170)
.attr("height", 20)
.attr("x", 845)
.attr("y", 110)
.style("fill", "url(#linear-gradient)");
gMap.append("text")
.attr("class", "label")
.attr("x", 780)
.attr("y", 125)
.text(minChange + "%");
gMap.append("text")
.attr("class", "label")
.attr("x", 1020)
.attr("y", 125)
.text(maxChange + "%");
//create initial line chart
lines(trashData, "101", g, ["101","0,0"]);
function lines(data, cd, g, legend) {
//monthly % change from 2019 values
var cdData = data.filter(d => d.boroCD == cd);
var changeValues = cdData.reduce (
(acc, rec) => [acc, {boroCD: rec.boroCD, date: rec.date,
total: +(rec.total/data.filter(it => (it.boroCD === rec.boroCD && it.date.includes("Jun 01 2019")))[0].total).toFixed(2),
mgp: +(rec.mgp/data.filter(it => (it.boroCD === rec.boroCD && it.date.includes("Jun 01 2019")))[0].mgp).toFixed(2),
paper: +(rec.paper/data.filter(it => (it.boroCD === rec.boroCD && it.date.includes("Jun 01 2019")))[0].paper).toFixed(2),
refuse: +(rec.refuse/data.filter(it => (it.boroCD === rec.boroCD && it.date.includes("Jun 01 2019")))[0].refuse).toFixed(2),
organics: +(rec.organics/data.filter(it => (it.boroCD === rec.boroCD && it.date.includes("Jun 01 2019")))[0].organics).toFixed(2)
}], []);
//draw line chart from LTR
var drawData = changeValues.sort(
(a, b) => new Date(a.date) - new Date(b.date));
//trash types
var keys = d3.keys(drawData[0]).filter(key => key !== "boroCD" && key !== "date");
//line chart date and trash type format/color
var timeParse = d3.timeParse("%b %d %Y");
drawData.map(d => d.date = timeParse(d.date));
var colorTypes = d3.scaleOrdinal(["#f73539", "#00cfe5", "#ff886f", "#77a4a9", "#378c78"])
.domain(keys);
var projections = keys.map(
function(type) {return { type: type, values: drawData.map(
function(d) { return {date: d.date, cd: d.boroCD, change: d[type]}; })};
});
//line chart axes
var x = d3.scaleUtc()
.range([5, width - margin.right])
.domain(d3.extent(drawData, d => d.date));
var y = d3.scaleLinear().range([height, 0])
.domain([0, 2])
.rangeRound([height - margin.bottom, margin.top]);
//line element
var line = d3.line()
.x(d => x(d.date))
.y(d => y(d.change))
.curve(d3.curveMonotoneX);
//allow for update
g.selectAll(".label").remove();
//explanatory texts + legend
g.append("text")
.data([legend])
.attr("class", "label")
.attr("x", 40).attr("y", 70)
.text(d => "monthly % change from June 2019 tonnage by type, community district " + d[0]);
g.append("text")
.attr("class", "label")
.attr("x", 720)
.attr("y", 80)
.text("click on a location to see monthly changes" );
g.selectAll("dots")
.data(keys)
.enter()
.append("circle")
.attr("cx", 20)
.attr("cy",(d,i) => 230 + i*20)
.attr("r", 5)
.style("fill", d => colorTypes(d));
g.selectAll("labels")
.data(keys)
.enter()
.append("text")
.attr("x", 30)
.attr("y",(d,i) => 235 + i*20)
.text(d => d)
.attr("text-anchor", "left")
.style("fill", d => colorTypes(d));
//allow for update
g.selectAll(".axis--x").remove();
// add the x axis
g.append("g")
.attr("transform", "translate(0, 330)")
.attr("class", "axis axis--x")
.call(d3.axisBottom(x));
g.selectAll(".tick line").remove();
//add the y axis
g.append("g")
.attr("class", "label")
.call(d3.axisLeft(y)
.tickValues(d3.ticks(y.domain(), 10))
.tickFormat(format()))
.call(g => g.selectAll(".tick line").clone()
.attr("stroke-opacity", d => d === 1 ? 0.4 : 0.1)
.attr("x2", width))
.call(g => g.select(".domain").remove())
function format(x) {
const f = d3.format("+.0%");
return x => x === 1 ? "0%" : f(x - 1);
}
//allow for update
g.selectAll(".proj").remove();
var proj = g.selectAll(".proj")
.data(projections)
.enter()
.append("g")
.attr("class", "proj");
proj.append("path")
.attr("class", "line")
.attr("id", (d, i) => "line" + i)
.attr("d", (d) => line(d.values))
.style("stroke", (d) => colorTypes(d.type))
.call(transition);
function transition(paths) {
paths.transition()
.duration(7000)
.attrTween("stroke-dasharray", tweenDash);
}
function tweenDash() {
var l = this.getTotalLength(),
i = d3.interpolateString("0," + l, l + "," + l);
return function(t) {
return i(t);
};
}
}
});
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |