index.html
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="stylesheet" href="//cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
<link href='https://fonts.googleapis.com/css?family=Merriweather' rel='stylesheet' type='text/css'>
<style>
body {
font-family: Merriweather, sans-serif;
background-color: linen;
font-size: 10pt;
padding: 20px;
}
p {
width: 700px;
}
#map {
height: 700px;
width: 700px;
}
.bio {
font-style: italic;
font-color: blue;
margin-left: 10px;
}
.facts {
margin-left: 5px;
font-style: bolder;
font-color: darkgray;
}
</style>
<body>
<h2>Every Rat in NYC (That People Complained About in 2016)</h2>
<p>Source: 2016 Rats <a href="https://data.cityofnewyork.us/Social-Services/Rat-Sightings/3q43-55fe">NYC Open Data on 311 calls for rat sightings</a>, via Jeremy Singer-Vine's newsletter. Click on a rat dot to learn about them. Their pics are from nearby, but may not have been taken by the rat personally. (Created with @GalaxyKate's <a href="https://github.com/galaxykate/tracery">Tracery.js</a> and the flickr API. For <a href="https://twitter.com/savasavasava">@savasavasava</a> and <a href="https://twitter.com/meetar">@meetar</a> and <a href="https://twitter.com/CaseyG">@CaseyG</a>.)</p>
<div id="map"></div>
<script src="//cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/queue.v1.min.js"></script>
<script src="https://code.jquery.com/jquery-1.12.2.min.js" integrity="sha256-lZFHibXzMHo3GGeehn1hudTAP3Sc0uKXBXAzHX1sjtk=" crossorigin="anonymous"></script>
<script src="tracery.js"></script>
<script>
var dateFormat = d3.time.format("%_m/%d/%y");
var searchDateFormat = d3.time.format("%Y-%m-%d");
var story;
var map = new L.Map("map", {center: [40.74, -73.9], zoom: 12})
.addLayer(L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles © Esri — Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
})
);
queue()
.defer(d3.csv, "Rat_SightingsNY_extract.csv", filterData)
.await(loaded);
function loaded(error, rats) {
if (error) throw error;
var ratMarkers = [];
rats.forEach(function(rat) {
var circle = L.circleMarker([+rat.Latitude, +rat.Longitude], {
stroke: false,
color: 'red',
fillColor: '#f03',
fillOpacity: 0.3,
className: 'rat'
}).setRadius(2).addTo(map);
circle.bindPopup("Loading...");
circle.on('click', function(e) {
var string = "<div class='facts'>" + dateFormat(rat.date) + "<br>Rat at " + rat["Location Type"] + ",<br>"
+ rat["Incident Address"] + "<br>" + rat.Borough + "</div><br><div class='bio'>";
string += getStory(rat);
e.target.bindPopup(string);
var url= getUrl(rat);
$.get(url).done(function(data) {
string += getPic(data);
string += "</div>";
e.target.bindPopup(string);
});
});
ratMarkers.push(circle);
});
map.on('zoomend', function() {
var currentZoom = map.getZoom();
var bounds = map.getBounds();
ratMarkers.forEach(function(r) {
if (bounds.contains(r.getLatLng())) {
if (currentZoom > 12) {
r.setRadius(4);
} else {
r.setRadius(2);
}
}
});
});
}
function filterData(d) {
var dateFormat = d3.time.format("%_m/%d/%y %H:%M");
d.date = dateFormat.parse(d["Created Date"]);
d.year = d.date.getFullYear();
if (d.year == 2016) {
return d;
}
}
function getRandom(list) {
function getRandomIntInclusive(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
return list[getRandomIntInclusive(0, list.length-1)];
}
function getUrl(rat) {
var baseUrl = "https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=e08843a8ba2ec0444013a39cf4082568&per_page=25&format=json&nojsoncallback=1&safe_search=1";
var date = "&max_taken_date=" + searchDateFormat(rat.date);
var search = "&lat=" + rat.Latitude.slice(0,5) + "&lon=" + rat.Longitude.slice(0,5) + date;
if (rat.Borough == "BROOKLYN") {
search += "&tags=Brooklyn,cheese,food,pub,rat,mouse,bar,home,cafe,coffee,beer,wine";
}
if (rat.Borough == "MANHATTAN") {
search += "&tags=nyc,NYC,NewYork,dinner,rat,mouse,food,street,subway,train,bus,cars,trash,restaurant";
}
return baseUrl + search;
}
function getPic(result) {
var photo = getRandom(result.photos.photo);
var farmId = photo.farm;
var serverId = photo.server;
var id = photo.id;
var secret = photo.secret;
var url = "https://farm" + farmId + ".staticflickr.com/" + serverId + "/" + id + "_" + secret + "_t.jpg";
return "<br>Last snap by our rat:<br><div class='img'><img src='" + url + "'/></div>";
}
function createStoryRules(rat) {
if (rat.Borough == "BROOKLYN") {
var food = ["artisinal cheeses", "raw vegan food", "probiotics", "pourover coffee with fairtrade beans from a bur grinder", "gluten-free mini cupcakes", "craft brews", "herbal cocktails", "imported Spanish chorizo", "fairtrade chocolate", "home-schooled children", "labradoodle pups"];
var doing = ["going to farmers markets", "lurking underfoot in brew pubs", "going for a long coffee with friends", "bathing in craft beers", "painting rat portraits on orange rinds", "posting on Medium", "making whisker lace for her Etsy shop", "shopping for cheeses online", "nesting in hipster beards", "pooping in hipster hats"];
} else if (rat.Borough == "MANHATTAN") {
var food = ["hot knishes", "candy bar wrappers", "mini cupcakes", "pizza slices", "lobster", "noodles", "dim sum", "fries", "McDonalds", "fresh bagels", "sushi", "Chinese food", "tacos"];
var doing = ["chasing subway trains", "lurking under platforms", "riding the third rail", "long scampers in Central Park", "visiting museums at night", "hiding in trash cans", "eating garbage", "preening on fire escapes", "getting #heroTheir# fur and nails done", "going for high tea", "shopping on the Upper East", "browsing in bookstores", "eating books and magazines", "nesting in rare books", "sleeping in the silver drawer"];
} else {
var food = ["Gouda", "Swiss Cheese", "ham and rye", "chocolate cake", "pie", "hotdogs", "bread crusts", "cold pizza", "old pizza", "potato chip crumbs", "cat food", "dog food", "nachos", "bagels", "donuts", "cronuts", "Italian food", "Chinese food", "French food", "kimchee", "cats", "roaches", "yogurt", "a good cupcake", "cookies"];
var doing = ["watching TV", "cleaning #heroTheir# fur", "teaching baby rats", "hanging in dark holes", "sharpening #heroTheir# teeth and claws", "tormenting cats", "teasing dogs", "sleeping", "long scampers on beaches", "running in parks", "meeting new single rats", "hanging with mice and other rodents", "swimming in sewage", "sampling people food", "playing chicken with subway trains", "playing in traffic", "sneaking up on people"];
}
var rules = {
"street": rat["Street Name"].split(" ")[0].toLowerCase() || "Ratface",
"mcRat": "Mc#street.capitalize#Face",
"name": ["Ratty #mcRat#", "'#food.capitalize#' Rat"],
"age": d3.range(0,30).map(function(d) {return d.toString();}),
"cheers": ["FTW!", "YOLO, right?", "Yay!", "Whatevs.", "Chillin'.", "GTD.", "No regrets!", "'S all good.", "It's a rat's life.", "Vermin FTW!", "Sometimes #heroThey# is like, 'wtf', though."],
"andDoing": doing,
"food": food,
"likes": ["#heroThey.capitalize# loves #food# and #andDoing#.", "#heroThey.capitalize# likes #food# and #andDoing#.", "#food.capitalize# really appeals to #heroThem#.", "#heroThey.capitalize# really enjoys some good #food#, but also #andDoing#."],
"setPronouns": ["[heroThey:she][heroThem:her][heroTheir:her][heroTheirs:hers]","[heroThey:he][heroThem:him][heroTheir:his][heroTheirs:his]"],
"Adj": ["a fine", "a passable", "an ambitious", "an exemplary", "a great", "a superior", "a lovely", "an ugly but lovable", "a fun", "a social", "a lazy", "an unpleasant", "a playful", "an annoying"],
"story": ["#name# is #Adj# rat. #heroThey.capitalize# was #age# weeks old at sighting. #likes# #cheers#"],
"origin": ["#[#setPronouns#]story#"]
};
return tracery.createGrammar(rules);
}
function getStory(rat) {
story = createStoryRules(rat);
return story.flatten("#origin#");
}
</script>