Skip to content

Instantly share code, notes, and snippets.

@kaz-a
Last active October 5, 2016 14:50
Show Gist options
  • Select an option

  • Save kaz-a/2f8e3ab16ae52096d10a to your computer and use it in GitHub Desktop.

Select an option

Save kaz-a/2f8e3ab16ae52096d10a to your computer and use it in GitHub Desktop.
NYC Air Quality Data Dashboard

Air Quality data dashboard for NYC DOHMH. This project is incomplete due to insufficient data(⋟﹏⋞)

<!DOCTYPE html>
<html>
<head>
<title>NYCCAS Dashboard</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href='https://fonts.googleapis.com/css?family=Oxygen:400,300,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" integrity="sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ==" crossorigin="anonymous">
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.css" />
<link rel = "stylesheet" type="text/css" href="style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js"></script>
<script src="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js" integrity="sha512-K1qjQ+NcF2TYO/eI3M6v8EiNYZfA95pQumfvcVrTHtwQVDG+aHRqLi/ETn2uB+1JqwYqVG3LIvdm9lj6imS/pQ==" crossorigin="anonymous"></script>
</head>
<style>
body{
/*font-family: arial,sans-serif;*/
font-family: 'Oxygen', sans-serif;
font-weight: 300;
overflow: none;
}
h1 {
font-weight: 700;
}
#map {
height: 500px;
}
.weatherCircle {
/*stroke: white;*/
fill: red;
opacity: 0.7;
}
.weatherCircle:hover {
fill: red;
stroke: red;
opacity: 1;
stroke-width: 10px;
}
div.tooltip {
position: absolute;
text-align: left;
width: auto;
height: auto;
padding: 8px;
font: 12px sans-serif;
background: white;
border: white 1px solid;
border-radius: 0px;
pointer-events: none;
}
.controller-button{
padding: 10px;
margin: 10px;
}
.info {
position: relative;
}
.info .overlay {
position: absolute;
top: 10;
left: 10;
pointer-events: none;
}
.button {
padding-bottom: 20px;
}
.weather {
float: right;
}
.pollutant-name {
opacity: 0.5;
font-weight: 300;
}
.current-date {
font-weight: 700;
}
</style>
<body>
<!-- DOHMH navbar -->
<nav class="navbar navbar-default navbar-dohmh">
<div class="container-fluid">
<!-- Hamberger -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="http://www.nyc.gov/html/doh/html/home/home.shtml" target="_blank"></a>
</div>
<!-- Navbar menu items -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li><a href="http://www.nyc.gov/nyc-resources/categories.page" target="_blank">NYC Resources</a></li>
<li><a href="http://www.nyc.gov/311/" target="_blank">311</a></li>
<li><a href="http://www.nyc.gov/office-of-the-mayor/" target="_blank">Office of the Mayor</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<!-- Content -->
<div class="container-fluid info">
<div class = "date col-xs-8 col-md-8">
<h1 class = "current-date"></h1>
<h1 class = "pollutant-name"></h1>
</div>
<div class = "weather col-xs-4 col-md-4">
<h2 class = "current-cond"></h2>
<p class = "current-temp"></p>
<p class = "wind-info"></p>
</div>
</div>
<div class = "container-fluid button"></div>
<div id="map"></div>
<script>
//set up leaflet
var map = L.map('map').setView([40.71, -74.00], 11);
//Initialize the SVG layer
map._initPathRoot();
//specify tile map service
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiemFrc2Nsb3NldCIsImEiOiJjaWdzZGh5ZjMwMmN1dGhrbnN6ZjFtb2NjIn0.x6b7Ra4Jdtbv38M9_uM2vQ', {
attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://mapbox.com">Mapbox</a>',
maxZoom: 18,
id: 'zakscloset.o4bigbgi',
accessToken: 'pk.eyJ1IjoiemFrc2Nsb3NldCIsImEiOiJjaWdzZGh5ZjMwMmN1dGhrbnN6ZjFtb2NjIn0.x6b7Ra4Jdtbv38M9_uM2vQ'
}).addTo(map);
//select svg
var svg = d3.select("#map").select("svg"),
g = svg.append("g");
//tooltip
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Parse the date / time
var parseDate = d3.time.format("%m/%d/%Y %H:%M").parse;
queue()
.defer(d3.csv, "https://raw.githubusercontent.com/Kaz-A/nyccas/master/NYCCAS_DEC_Weather_Results.csv")
.await(ready);
function ready(error, data) {
if (error) throw error;
//console.log(data);
//Add a LatLng object to each item in the dataset
data.forEach(function(d) {
d.LatLng = new L.LatLng(d.Latitude,
d.Longitude);
//format date
d.date = parseDate(d.StartTime);
d.value = +d.Value;
});
// var transform = d3.geo.transform({ point: projectPoint }),
// path = d3.geo.path().projection(transform);
//create an array by starttime and pollutant
var nestedByDate = d3.nest()
.key(function(d) { return d.StartTime; })
.key(function(d) { return d.parameter; })
//.map(data);
.entries(data);
//console.log(nestedByDate);
//get the latest data for all pullutants
var latest = Object(nestedByDate).reverse()[1];
console.log(latest);
//create buttons
var buttons = d3.select(".button").selectAll(".button")
.data(latest.values.map(function(d) { return (d.key); }))
.enter()
.append("button")
.attr("class", "btn btn-default")
.text(function(d) { return d; })
.on("click", function(d) { drawResults(d); });
//get the latest data for pm2.5
var pm25 = latest.values[7];
console.log(pm25);
//print latest data collection date/time and weather info
d3.select(".current-date").html("Air Monitoring Results for " + "<br />" + latest.key);
d3.select(".current-cond").html(pm25.values[0]["weather"]);
d3.select(".current-temp").html(pm25.values[0]["temp_f"] + " &deg;F");
d3.select(".wind-info").html("Wind from the " + pm25.values[0]["wind_dir"] + " at " + pm25.values[0]["wind_mph"] + " mph, gusting to " + pm25.values[0]["wind_gust_mph"] + " mph. Windchill at " + pm25.values[0]["windchill_f"] + "&deg;F." + "<br />" + "(Observed @ " + pm25.values[0]["station_id"] + ") ");
d3.select(".pollutant-name").html(pm25.key);
//determine the extent of the pm2.5 values
console.log(d3.extent(pm25.values.map(function(d) { return (d.Value);})));
var rExtent = d3.extent(pm25.values.map(function(d) { return (d.value); }));
console.log(rExtent);
var radius = d3.scale.linear()
.domain(rExtent)
.range([5, 25]);
//append pm2.5 data
var weatherCircle = g.selectAll("circle")
.data(pm25.values)
.enter()
.append("circle")
.attr("class", "weatherCircle")
.attr("r", function(d) { return radius(d.Value)})
.on("mouseover", function(d){
tooltip.html("<h4>" + d.Site + "</h4>" + "<br />" + d.Address + "<br />" + d.parameter + " = " + d.Value + " " + d.Units
+ "<br />" + "Measured at " + d.Location + "<br />" + d.StartTime)
.style("opacity", 0.8)
.style("left", (d3.event.pageX)+6 + "px")
.style("top", (d3.event.pageY)-80 + "px");
})
.on("mouseout", function(d) {
tooltip.style("opacity", 0);
});
//rescale/reposition data on zoom/pan
map.on("viewreset", update);
update();
//function to update the location of circles
function update() {
weatherCircle.attr("transform", function(d) {
return "translate("+
map.latLngToLayerPoint(d.LatLng).x +","+
map.latLngToLayerPoint(d.LatLng).y +")";
})
};
//draw line chart for pm2.5
function drawLinechart(pm25) {
}
//update the map on button-click
//(I'm using the raw data instead of nested data for this function for now. Will update this code later.)
function drawResults(pollutant) {
// var co = latest.values[0];
//var thisPollutant = latest.values;
var thisPollutant = data.filter(function(d) { return d.parameter === pollutant; });
//console.log(thisPollutant);
//get the last date
var lastDate = thisPollutant.map(function(d) { return d.StartTime; });
var thisLatestPollutant = thisPollutant.filter(function(d) { return d.StartTime === lastDate[lastDate.length-1]; });
console.log(thisLatestPollutant);
//determine the extent of values for each pollutant
console.log(d3.extent(thisLatestPollutant.map(function(d) { return (d.Value);})));
var rExtentEach = d3.extent(thisLatestPollutant.map(function(d) { return (d.value); }));
console.log(rExtentEach);
var radiusEach = d3.scale.linear()
.domain(rExtentEach)
.range([5, 25]);
//read data join
pollutionData = svg.selectAll(".weatherCircle")
.data(thisLatestPollutant);
pollutionData.exit().remove()
.transition()
.style('opacity', 0);
// enter NEW elements from data join
pollutionDataEnter = pollutionData.enter()
.append("g")
.append("circle")
.attr("class", "weatherCircle")
.attr("r", function(d) { return radiusEach(d.Value); })
.on("mouseover", function(d){
tooltip.html("<h4>" + d.Site + "</h4>" + "<br />" + d.Address + "<br />" + d.parameter + " = " + d.Value + " " + d.Units
+ "<br />" + "Measured at " + d.Location + "<br />" + d.StartTime)
.style("opacity", 0.8)
.style("left", (d3.event.pageX)+6 + "px")
.style("top", (d3.event.pageY)-80 + "px");
})
.on("mouseout", function(d) {
tooltip.style("opacity", 0);
});
//rescale/reposition data on zoom/pan
map.on("viewreset", updateEach);
updateEach();
//function to update the location of circles
function updateEach() {
pollutionData = svg.selectAll(".weatherCircle")
.transition()
.duration(1000)
.attr("transform", function(d) {
return "translate("+
map.latLngToLayerPoint(d.LatLng).x +","+
map.latLngToLayerPoint(d.LatLng).y +")";
})
.select(".weatherCircle")
.attr("r", function(d) { return radiusEach(d.Value); })
// .style("opacity", 0.7)
// .attr("fill", "red");
};
//print pollutant name on title
d3.select(".pollutant-name")
.text([pollutant]);
}
};
</script>
</body>
/*** Works on common browsers ***/
::selection {
background-color: #53c2c4;
}
/*** Mozilla based browsers ***/
::-moz-selection {
background-color: #53c2c4;
}
/***For Other Browsers ***/
::-o-selection {
background-color: #53c2c4;
}
::-ms-selection {
background-color: #53c2c4;
}
/*** For Webkit ***/
::-webkit-selection {
background-color: #53c2c4;
}
a {
color: black;
}
a:hover {
color: red;
text-decoration: none;
}
a:focus {
outline: none;
color: black;
text-decoration: none;
}
a.navbar-brand {
background-image: url("https://raw.githubusercontent.com/Kaz-A/nyccas/master/images/dohmh_logo.png");
background-size: cover;
width: 109px;
height: 70px;
margin-top: -28px;
}
.navbar-default {
background-color: transparent;
border-color: transparent;
}
.navbar-dohmh {
background-color: black;
border-radius: 0px;
}
.navbar-default .navbar-nav>li>a {
color: white;
}
.navbar-default .navbar-nav>li>a:hover {
color: red;
}
.navbar-default .navbar-nav>li>a:focus {
color: white;
}
.navbar {
margin-bottom: 0px;
}
.navbar-epht {
background-color: beige;
border-radius: 0px;
}
.navbar-default .navbar-toggle {
border-color: transparent;
}
.btn {
border-radius: 0px !important;
transition: linear, ease-in 0.3s !important;
}
.btn:focus {
color: white !important;
}
.btn-default {
color: black !important;
/*background: transparent !important;*/
border: black 0px solid !important;
padding: 2% !important;
}
.btn-default:hover {
color: white !important;
background: black !important;
border-color: black !important;
}
.btn-default:focus {
outline: none !important;
text-decoration: none !important;
color: black !important;
}
.btn-default:active {
background: orange !important;
}
.btn-default a {
color: black !important;
}
.btn-default a:focus {
color: red !important;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment