Air Quality data dashboard for NYC DOHMH. This project is incomplete due to insufficient data(⋟﹏⋞)
Last active
October 5, 2016 14:50
-
-
Save kaz-a/2f8e3ab16ae52096d10a to your computer and use it in GitHub Desktop.
NYC Air Quality Data Dashboard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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 © <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"] + " °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"] + "°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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /*** 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