Skip to content

Instantly share code, notes, and snippets.

@kaz-a
Last active June 8, 2017 19:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kaz-a/3d3f4f2a05f8029d060e66cb03e4ce4b to your computer and use it in GitHub Desktop.
Save kaz-a/3d3f4f2a05f8029d060e66cb03e4ce4b to your computer and use it in GitHub Desktop.
EPHT Report Tiles
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<title>EPHT Report</title>
<meta charset="utf-8" http-equiv="X-UA-Compatible" content="IE=9" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
<script src="https://use.fontawesome.com/5434c9593b.js"></script>
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="css/indicator.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<style>
body{
font-family: 'Open Sans', sans-serif;
overflow: none;
}
.center{
text-align: center;
}
.tile {
height: 400px;
border: white 10px solid;
padding: 20px;
}
.indicator_tile {
font-family: 'Open Sans', sans-serif;
}
.indicator_name {
font-weight: bold;
font-size: 15px;
padding: 10px 30px;
}
.neighborhood_value, .borough_value, .nyc_value {
margin: 20px 0 0 0;
}
.neighborhood_value>span, .borough_value>span, .nyc_value>span {
font-size: 25px;
font-weight: thin;
}
.svg {
padding: 60px 40px;
height: 150px;
width: 100%;
}
.barSvg {
padding: 0 40px;
height: 30px;
width: 100%;
}
div.tooltip {
position: absolute;
text-align: left;
width: auto;
height: auto;
padding: 8px;
font: 12px sans-serif;
background: black;
border-radius: 0px;
pointer-events: none;
color: white;
}
</style>
</head>
<body>
<div class='indicator_tile'>
</div>
</body>
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
function getUrl(reportId, geoTypeId, geoEntityId){
var baseUrl = "http://a816-dohmeta.nyc.gov/MetadataLite/api/Report/GetReportData?";
// need to map the name to the id - actually, mapping the address to the name would be better imo because we're searching by address
// var reportId = 78; // convert user selection "Asthma and the Environment" to report_id: 78
// var geoTypeId = 3; // constant - always 3
// var geoEntityId = 201; // convert user input "Greenpoint" to geo_entity_id: 201
var url = baseUrl + "report_id=" + reportId + "&geo_type_id=" + geoTypeId + "&geo_entity_id=" + geoEntityId;
return url;
}
// sample inputs - replace these with user input
var geoType = 3;
var topicInput = 78;
var neighborhoodCode = 201;
var tabInput = "Indoor Air Quality";
console.log(topicInput, neighborhoodCode, tabInput);
var json = getUrl(topicInput, geoType, neighborhoodCode);
var data = d3.json(json, ready);
// create a function that returns the data as the indicatorData object
function getIndicatorData(data){
var reportContent = data.report.report_content;
var dataByTabItem = {};
reportContent.forEach(function(i){
var reportTopic = i.report_topic;
var reportTopicData = i.report_topic_data;
dataByTabItem[reportTopic] = reportTopicData;
})
var indicatorData = dataByTabItem[tabInput];
console.log(dataByTabItem);
return indicatorData;
}
// using the data from getIndicatorData, create a tile with indicator name, values, rank (color) and a trendchart
function createIndicatorTile(data){
var indicatorData = getIndicatorData(data);
console.log("indicatorData:", indicatorData);
indicatorData.forEach(function(d){
var tile = d3.select(".indicator_tile").append("div").attr("class", "tile center col-xs-12 col-sm-12 col-md-4");
// indicator name
tile.append("div").attr("class", "indicator_name row").html(d.indicator_name);
// area values
var vals = tile.append("div").attr("class", "indicator_vals row");
vals.append("div").attr("class", "neighborhood_value col-xs-4 col-sm-4 col-md-4").html("Neighborhood<br><span>" + Math.round(d.unmodified_data_value_geo_entity) + "</span>");
vals.append("div").attr("class", "borough_value col-xs-4 col-sm-4 col-md-4").html("Borough<br><span>" + Math.round(d.unmodified_data_value_borough) + "</span>");
vals.append("div").attr("class", "nyc_value col-xs-4 col-sm-4 col-md-4").html("NYC<br><span>" + Math.round(d.data_value_nyc) + "</span>");
// rank color
var better = "#97ddda", middle = "#eded6e", worse = "#e8d1d1";
var better_dark = "#2e8d92", middle_dark = "#d4ab00", worse_dark = "#ba8c8c";
tile.style("background", function(){
return d.data_value_rank === 1 ? better : d.data_value_rank === 2 ? middle : worse;
});
// chart div
var chartDiv = tile.append("div").attr("class", "chart row");
var elem = d3.select(".chart").node();
var w = elem.getBoundingClientRect().width;
var h = 100;
// bar chart
var barChartDiv = chartDiv.append("div").attr("class", "bar_chart");
var barElem = d3.select(".bar_chart").node();
var bar_w = barElem.getBoundingClientRect().width;
var bar_h = 40;
var barSvg = barChartDiv.append("svg").attr("class", "barSvg"),
margin = {top: 0, right: 40, bottom: 10, left: 40},
width = bar_w - margin.left - margin.right,
height = bar_h - margin.top - margin.bottom,
g = barSvg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
// create the area values dataset
// we want the data to be:
// ---------------------
// area val
// ------------ ----
// NYC 4.9
// borough 4.3
// neighborhood 5.0
// ---------------------
// ---> so barData should be:
// [{ area: "nyc", value: 4.9 }, { area: "borough", value: 4.3 }, { area: "neighborhood", value: 5.0 }]
// console.log(d);
var barData = function(x, y){
var barVals = {};
barVals["area"] = x;
barVals["value"] = y;
return barVals;
};
for(var key in d){
var nycVal = barData("nyc", d.data_value_nyc);
var boroughVal = barData("borough", d.unmodified_data_value_borough);
var neighborhoodVal = barData("neighborhood", d.unmodified_data_value_geo_entity);
}
var barData = [ neighborhoodVal, boroughVal, nycVal ];
console.log('barData', barData);
x.domain(barData.map(function(d) { return d.area; }));
y.domain([0, d3.max(barData, function(d) { return d.value; })]);
barSvg.selectAll(".bar")
.data(barData)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.area); })
.attr("y", function(d) { return y(d.value); })
.attr("fill", function(){
return d.data_value_rank === 1 ? better_dark : d.data_value_rank === 2 ? middle_dark : worse_dark;
})
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.value); });
// rank text
var rank = d.data_value_rank;
var rankTextDiv = chartDiv.append("div").attr("class", "rank_text");
var rankText = rankTextDiv.append("text").text(function(){
return rank === 1 ? "Better than other neighborhoods" :
rank === 2 ? "About the same as other neighborhoods" :
"Worse than other neighborhoods";
});
rankText.style("color", function(){
return rank === 1 ? better_dark : rank === 2 ? middle_dark : worse_dark;
})
// trend line chart
var trendData = d.trend_over_time;
// console.log("trendData:", trendData);
var formatYear = d3.timeParse("%Y-%m-%d");
var lineChartDiv = chartDiv.append("div").attr("class", "line_chart");
var lineElem = d3.select(".line_chart").node();
var w = lineElem.getBoundingClientRect().width;
var h = 40;
var svg = lineChartDiv.append("svg").attr("class", "svg"),
margin = {top: 0, right: 40, bottom: 0, left: 40},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
console.log("chart height: ", height, "chart width:", width);
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// if there is 1 or less data points, show a message instead
if(trendData.length <= 1){
svg.append("text")
.attr("x", "50%")
.attr("y", "30%")
.attr("text-anchor", "middle")
.attr("font-family","FontAwesome")
.attr("font-size", function(d) { return "30px";} )
.html(function(d) { return "&#xf119;"; }); // frown face :^( we can remove this
svg.append("text").text("Insufficient data for trendline")
.attr("x", "50%").attr("y", "100%").attr("text-anchor", "middle");
} else {
trendData.forEach(function(d){
year = d.year;
var x = d3.scaleTime().rangeRound([0, width]);
var y = d3.scaleLinear().rangeRound([height, 0]);
var xAxis = d3.axisBottom().scale(x);
var yAxis = d3.axisLeft().scale(y);
var nestedData = d3.nest()
.key(function(d) { return d.start_period; })
.entries(trendData);
// console.log("nestedData:", nestedData);
drawChart();
function drawChart() {
var line = d3.line()
.x( function(d) { return x(formatYear(d.key)) } )
.y( function(d) { return y(d.values[0].data_value) } );
x.domain( d3.extent( nestedData, function(d) { return formatYear(d.key) } ) );
y.domain( [ 0, d3.max( nestedData, function(d) { return d.values[0].data_value } ) ]);
var trendLine = svg.selectAll(".trendLine")
.data(nestedData)
.enter()
.append("g")
.attr("class", "trendLine")
.attr("id", function(d) { return d.key } );
trendLine.append("path")
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", function(d) { return line(nestedData); } );
var circleWidth = 4;
var circle = trendLine.selectAll("circle")
.data(nestedData)
.enter()
.append("circle")
.attr("r", circleWidth)
.attr("cx", function(d) { return x(formatYear(d.key)); })
.attr("cy", function(d) { return y(d.values[0].data_value); })
.attr("fill", function(){
return rank === 1 ? better : rank === 2 ? middle : worse;
})
.attr("stroke", "#000")
.attr("opacity", 1) // change to 0 for hiding datapoint circles
.style("cursor", "pointer")
.on("mouseover", function(d) {
d3.select(this).style("opacity", 1)
.attr("fill", function(){
return rank === 1 ? better_dark : rank === 2 ? middle_dark : worse_dark;
})
.attr("r", circleWidth * 2)
tooltip.text(d.values[0].year + ": " + d.values[0].data_value)
.style("opacity", 0.7)
.style("left", (d3.event.pageX)-30 + "px")
.style("top", (d3.event.pageY)-50 + "px");
})
.on("mouseout", function(d) {
d3.select(this).style("opacity", 1) // change to 0 for hiding datapoint circles
.attr("fill", function(){
return rank === 1 ? better : rank === 2 ? middle : worse;
})
.attr("r", circleWidth);
tooltip.style("opacity", 0);
});
var circleLabel = trendLine.selectAll("text")
.data(nestedData)
.enter()
.append("text")
.filter(function(d, i) { return i === 0 || i === (nestedData.length - 1) })
.text(function(d) { return d.values[0].year; })
.attr("x", function(d) { return x(formatYear(d.key)); })
.attr("y", function(d) { return y(d.values[0].data_value) + 14; })
.attr("text-anchor", function(d, i){ return i === 0 ? "left" : "end"; })
.attr("font-size", "10px")
.attr("fill", "#000");
}
});
}
})
}
// render tiles
function ready(error, data) {
if (error) throw error;
createIndicatorTile(data);
}
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment