Skip welcome & menu and move to editor
Welcome to JS Bin
Load cached copy from
 
<!DOCTYPE html>
<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

Dismiss x
public
Bin info
ursulakaczmarekpro
0viewers