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">
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <script src="https://d3js.org/d3-time-format.v2.min.js"></script>
  <title>nyc auto smash-and-grabs</title>
  <style>
    h1 {
      font: 100 22px "Montserrat", sans-serif;
      color: #969696;
    }
    h2 {
      font: 100 16px "Montserrat", sans-serif;
      color: #969696;
    }
    p,
    .ticks {
      font: 100 12px "Montserrat", sans-serif;
      color: #969696;
    }
    .label {
      font: 500 16px "Montserrat", sans-serif;
      fill: #ddd;
    }
    .pct {
      stroke-width: 0.2;
    }
    .track {
      stroke: #000;
      stroke-opacity: 0.3;
      stroke-width: 10px;
      stroke-linecap: round;
    }
    .track-inset {
      stroke: #dcdcdc;
      stroke-width: 8px;
      stroke-linecap: round;
    }
    .track-overlay {
      pointer-events: stroke;
      stroke-width: 25px;
      stroke: transparent;
      cursor: crosshair;
    }
    .handle {
      fill: #fff;
      stroke: #000;
      stroke-opacity: 0.5;
      stroke-width: 1px;
    }
    #button {
      position: absolute;
      top: 140px;
      left: 20px;
      background: #5e81a3;
      border: none;
      color: white;
      padding: 0px 5px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 14px;
      cursor: pointer;
      width: 80px;
      height: 30px;
    }
    #button:hover {
      background-color: #00528b;
    }
  </style>
</head>
<body>
  <h1>NYC auto smash-and-grabs since 2019</h1>
  <h2></h2>
  <button id="button" style="vertical-align:middle"><span>start </span></button>
  <div id="chart">
  </div>
  <script>
    var pctUrl = "https://data.cityofnewyork.us/resource/5rqd-h5ci.geojson";
    //use app token to avoid throttling:"https://data.cityofnewyork.us/resource/qgea-i56i.json?$$app_token=YOUR_TOKEN&$where=pd_cd IN('321.0','421.0') AND rpt_dt>'2019-3-31' AND crm_atpt_cptd_cd='COMPLETED' AND lat_lon IS NOT NULL&$limit=100000"
    var histUrl = "https://data.cityofnewyork.us/resource/qgea-i56i.json?$where=pd_cd IN('321.0','421.0') AND rpt_dt>'2019-4-30' AND crm_atpt_cptd_cd='COMPLETED' AND lat_lon IS NOT NULL&$limit=100000";
    var ytdUrl = "https://data.cityofnewyork.us/resource/5uac-w243.json?$where=pd_cd IN('321.0','421.0')AND crm_atpt_cptd_cd='COMPLETED' AND lat_lon IS NOT NULL&$limit=100000";
    var formatDateYear = d3.timeFormat("%b %Y")
    Promise.all([d3.json(pctUrl), d3.json(histUrl), d3.json(ytdUrl)])
      .then(function(allSets) {
        var precincts = allSets[0];
        var histCrimes = allSets[1];
        var ytdCrimes = allSets[2];
        var combinedData = d3.merge([histCrimes, ytdCrimes]);
        var crimeData = combinedData.map(
          row => [{
            number: row.cmplnt_num,
            reportDate: new Date(row.rpt_dt),
            offenseCode: row.pd_cd,
            offense: row.pd_desc,
            lat: +Number(row.latitude),
            long: +Number(row.longitude)
          }]).flat().sort((a, b) => a.reportDate - b.reportDate);;
        var startDate = crimeData.map(d => d.reportDate)[0],
          endDate = crimeData.map(d => d.reportDate).reverse()[0];
        var margin = {
            top: 0,
            right: 10,
            bottom: 5,
            left: 25
          },
          width = 800 - margin.left - margin.right,
          height = 650 - margin.top - margin.bottom;
        //create top-level SVG
        var svg = d3.select("#chart").append("svg")
          .attr("width", width + margin.left + margin.right)
          .attr("height", height + margin.top + margin.bottom);
        var projection = d3.geoMercator()
          .scale(60000)
          .translate([width / 1.5, height])
          .center([-73.94, 40.50]);
        //nypd pct outlines
        var path = d3.geoPath()
          .projection(projection);
        var targetValue = width - 25,
          inputValue = 0;
        var x = d3.scaleTime()
          .domain([startDate, endDate])
          .range([0, targetValue])
          .clamp(true);
        var slider = svg.append("g")
          .attr("class", "slider")
          .attr("transform", "translate(" + margin.left + "," + height / 20 + ")");
        var startButton = d3.select("#button"),
          moving = false;
        slider.append("line")
          .attr("class", "track")
          .attr("x1", x.range()[0])
          .attr("x2", x.range()[1])
          .select(function() {
            return this.parentNode.appendChild(this.cloneNode(true));
          })
          .attr("class", "track-inset")
          .select(function() {
            return this.parentNode.appendChild(this.cloneNode(true));
          })
          .attr("class", "track-overlay")
          .call(d3.drag()
            .on("start.interrupt", function() { slider.interrupt(); })
            .on("start drag", function() {
              inputValue = d3.event.x;
              update(x.invert(inputValue));
            })
          );
        slider.insert("g", ".track-overlay")
          .attr("class", "ticks")
          .attr("transform", "translate(30," + margin.left + ")")
          .selectAll("text")
          .data(x.ticks(9))
          .enter()
          .append("text")
          .attr("x", x)
          .attr("y", 10)
          .attr("text-anchor", "middle")
          .text(d => formatDateYear(d));
          startButton.on("click", function() {
            var button = d3.select(this);
            if (button.text() == "pause") {
              moving = false;
              clearInterval(timer);
              button.text("start");
            } else {
              moving = true;
              timer = setInterval(step, 100);
              button.text("pause");
            }
          })
          function step() {
            update(x.invert(inputValue));
            inputValue = inputValue + (targetValue / 11);
            if (inputValue > targetValue) {
              moving = false;
              inputValue = 0;
              clearInterval(timer);
              playButton.text("start");
            }
          }
        var handle = slider.insert("circle", ".track-overlay")
          .attr("class", "handle")
          .attr("r", 9);
        var label = slider.append("text")
          .attr("class", "label")
          .attr("text-anchor", "middle")
          .text(formatDateYear(startDate))
          .attr("transform", "translate(0," + (-20) + ")");
        svg.selectAll(".pct")
          .data(precincts.features)
          .enter().append("path")
          .attr("class", "pct")
          .style("fill", "#ddd")
          .style("stroke", "#969696")
          .attr("d", path);
        draw(crimeData, startDate);
        function draw(data, date) {
          svg.selectAll("locations").remove();
          var locations = svg.selectAll("locations")
            .data(data)
            .enter().append("circle")
            .attr("cx", d => projection([d.long, d.lat])[0])
            .attr("cy", d => projection([d.long, d.lat])[1])
            .attr("r", 2);
          locations.style("fill", function(d, i) {
              var d = formatDateYear(d.reportDate);
              if (d === formatDateYear(date)) {
                this.parentElement.appendChild(this);
                return "#ff0000";
              } else {
                return "lightgrey";
              };
            })
            .style("stroke", "#9696")
            .style("opacity", 0.8);
        }
        function update(h) {
          handle.attr("cx", x(h));
          label.attr("x", x(h))
            .text(formatDateYear(h));
          draw(crimeData, h);
        }
      });
  </script>
</body>
<footer>
  <p>data <a href="https://data.cityofnewyork.us/resource/qgea-i56i" style="color:#969;text-decoration:none">nyc open data </a>
    created by <a href="https://ursulams.github.io/" style="color:#969;text-decoration:none">ursula kaczmarek</a></p>
</footer>
</html>
Output 300px

You can jump to the latest bin by adding /latest to your URL

Dismiss x
public
Bin info
ursulakaczmarekpro
0viewers