Last active
August 29, 2015 14:22
-
-
Save mapsense-examples/52732b29ec244f470777 to your computer and use it in GitHub Desktop.
Interactive census map
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
html, body, #myMap { | |
width: 100%; | |
height: 100%; | |
margin: 0; | |
overflow: hidden; | |
font: 14px 'Droid Sans', sans-serif; | |
color: #666; | |
} | |
.ms { | |
stroke-width: 1; | |
stroke: #ccc; | |
} | |
#ui { | |
position: absolute; | |
z-index: 99; | |
margin: 0; | |
top: 0; | |
left: 0; | |
clear: both; | |
} | |
#selector { | |
font: 20px 'Droid Sans', sans-serif; | |
} | |
#legend { | |
background: rgba(255,255,255,0.8); | |
overflow: auto; | |
display: inline-block; | |
} | |
#color-chips, #color-values { | |
float: left; | |
margin-left: 5px; | |
} | |
#color-values{ | |
white-space: pre; | |
padding-right: 5px; | |
} | |
#color-values textarea { | |
overflow:hidden; | |
margin: 0; | |
border: none; | |
resize: none; | |
height: 100%; | |
} | |
.mouseinfo { | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
pointer-events: none; | |
font: 20px 'Droid Sans', sans-serif; | |
} | |
table { | |
border-collapse: collapse; | |
} | |
table, th, td { | |
border: 1px solid #ddd; | |
padding: 0 2px; | |
} | |
.detailKey { | |
background: #eee; | |
opacity: .8; | |
text-transform: uppercase; | |
font-weight: 600; | |
} | |
.detailVal { | |
background: rgba(255,255,255,0.8); | |
} |
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> | |
<meta charset="utf-8"> | |
<meta name="viewport" content='initial-scale=1,maximum-scale=1,user-scalable=no' /> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script src="http://d3js.org/topojson.v1.min.js" charset="utf-8"></script> | |
<script src="https://developer.mapsense.co/mapsense.js" charset="utf-8"></script> | |
<link type="text/css" href="https://developer.mapsense.co/mapsense.css" rel="stylesheet"/> | |
<link type="text/css" href="index.css" rel="stylesheet"/> | |
</head> | |
<body> | |
<div id="myMap"></div> | |
<div id="ui"> | |
<div id="control"></div> | |
<div id="legend"> | |
<div id="color-chips"></div> | |
<div id="color-values"></div> | |
</div> | |
</div> | |
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js' type="text/javascript"></script> | |
<script src='http://d3js.org/colorbrewer.v1.min.js' type="text/javascript"></script> | |
<script> | |
var map; | |
var the_key = "key-2d5eacd8b924489c8ed5e8418bd883bc"; | |
var us50 = [ // we'll set the map extent to these bounds | |
{lon: -156, lat: 19.1}, | |
{lon: -66.4, lat: 55.9} | |
]; | |
var G = {}; // A global object to store the field variables | |
G.field_stats = {}; | |
G.FIELD = "age"; | |
G.fields_arr = []; | |
var colorQuantile, | |
schema_url, | |
demographics_url, | |
demographics_layer, | |
selectedScheme = 'Blues', | |
numClasses = 9, | |
F_COMMA = d3.format(",.0f"); | |
// We use the schema_url to get the list of fields available and add them to the UI selector | |
schema_url = 'https://explore.mapsense.co/explore/api/schema?api-key=' + the_key; | |
schema_url += '&universe=mapsense.demographics'; | |
demographics_url = "https://{S}-api.mapsense.co/universes/mapsense.demographics/{Z}/{X}/{Y}.topojson?api-key=" + the_key; | |
demographics_url += "&where=name!='Puerto Rico' AND layer=='state'"; | |
demographics_url += "&ringSpan=10&lineSpan=10&s=10"; // params to simplify geoms (https://developer.mapsense.co/documentation/tileQueries) | |
// create a color ramp | |
colorQuantile = d3.scale.quantile() | |
.domain([0,1]) | |
.range(colorbrewer[selectedScheme][numClasses]) | |
; | |
initSelect(); // initialize the selector UI | |
initMap(); // initialize the map | |
// Add a div to display info mouseover info | |
var mouseinfo = d3.select('body') | |
.append("div") | |
.attr("class","mouseinfo"); | |
function initMap() { | |
var param_selection_function = (function(whitelist) { | |
return function(s) { | |
// Update the colors and legend based on the max and min of the current global FIELD | |
// max & min expand to include values for new tiles (but don't contract) | |
updateAll(); | |
s.attr("class", "ms"); // Assign a class (ms = mapsense) | |
s.on("mouseover", function(d) { // Bind a function to mouseover | |
// This will build a table to display the field names and values | |
var text = ""; | |
var value; | |
text += '<div class="detailCard"><table><tbody>'; | |
if (whitelist.length > 0) { // if there's a whitelist, only show those fields | |
for (var i = 0; i < whitelist.length; i++) { | |
key = whitelist[i]; | |
if (d.properties && d.properties[key]) { | |
value = d.properties[key]; | |
value = formatValue(value); | |
text += '<tr><td class="detailKey">' + key + '</td><td class="detailVal">' + value + '</td></tr>'; | |
} | |
} | |
} else { // if no whitelist, show all fields | |
for (var key in d.properties) { | |
text += '<tr><td class="detailKey">' + key + '</td><td class="detailVal">' + d.properties[key] + '</td></tr>'; | |
} | |
} | |
mouseinfo.html(text); // Update the mouseinfo div with the dynamic info | |
// Finally, assign this function to the selection | |
// and request the values for name, population, and the currently selected field option | |
demographics_layer.selection(param_selection_function(['name',G.FIELD,'population'])); | |
}); | |
}; | |
}); | |
map = mapsense.map("#myMap"); // init the map | |
//map.zoom(3).center({lon: -99, lat: 38}); | |
map.extent(us50); | |
demographics_layer = mapsense.topoJson() | |
.url(mapsense.url(demographics_url).hosts(['a', 'b', 'c', 'd'])) | |
.clip(true) | |
.selection( param_selection_function(['name',G.FIELD,'population']) ) | |
.on("load", applyColors) | |
; | |
map.add(demographics_layer); | |
// change map interaction so users can see the map update when they scroll through the selector fields | |
map.interact(false); | |
map.add(mapsense.drag()); | |
map.add(mapsense.wheel()); | |
map.add(mapsense.dblclick()); | |
map.add(mapsense.touch()); | |
map.add(mapsense.attribution('<a target="_blank" href="https://developer.mapsense.co/tileViewer/?tileset=mapsense.demographics">Mapsense Demographics</a>')); | |
$("#selector").val(G.FIELD).change(); // trigger the selector | |
} | |
function updateAll() { | |
applyColors(); // for the map | |
drawColorChips(); // for the legend | |
updateChipValues(); // for the legend | |
} | |
function getStats(selxn) { | |
selxn.each(function(d,i){ | |
if (d.properties && d.properties[G.FIELD]) { | |
var field_value = d.properties[G.FIELD]; | |
if ( parseFloat(field_value) > G.field_stats[G.FIELD].max) { | |
G.field_stats[G.FIELD].max = +field_value; | |
} | |
if ( +field_value < G.field_stats[G.FIELD].min) { | |
G.field_stats[G.FIELD].min = +field_value; | |
} | |
} | |
}); | |
} | |
function applyColors(e) { | |
if (G.FIELD && G.field_stats[G.FIELD]) { | |
d3.selectAll('.ms').call(getStats); | |
d3.selectAll('.ms') | |
.style("fill", function(d,i) { | |
colorQuantile = d3.scale.quantile() | |
.domain([G.field_stats[G.FIELD].min, G.field_stats[G.FIELD].max]) | |
.range(colorbrewer[selectedScheme][numClasses]) | |
; | |
return colorQuantile(d.properties[G.FIELD]); | |
}); | |
} | |
} | |
function initSelect() { | |
d3.json(schema_url, function(json) { | |
var skip_fields = ['layer','area','name','id','minz']; | |
// the names of fields are embed in the requested json | |
// here we map them to a simpler array | |
G.fields_arr = json.schema.fields | |
.map(function(d){ | |
// include field if its values are numeric | |
// otherwise will return undefined | |
if (d.type !== 'string') return d.name; | |
}) | |
; | |
// only include field if it's truthy (removes undefined values) | |
G.fields_arr = G.fields_arr.filter(function (el) { | |
return el; | |
}); | |
// if the field is in our list of skip_fields, then skip it | |
G.fields_arr = G.fields_arr.filter(function (el) { | |
return skip_fields.indexOf(el) == -1; | |
}); | |
// add a select element to the page | |
d3.select('#control').append('select') | |
.attr('id','selector') | |
.selectAll('option') // there aren't any option elements yet, so we... | |
.data(G.fields_arr) // bind the list of fields... | |
.enter() // and when we enter the selection... | |
.append('option') // we append an option | |
.text(function(d){return d;}) // and add the field name (d being an element of fields_arr) | |
; | |
// Insert the fields_arr into our global object, so it can also store max & min... | |
// needed for scaling the colors for each unique domain of values. | |
for (var i = 0; i < G.fields_arr.length; i++) { | |
G.field_stats[G.fields_arr[i]] = {max: -Infinity, min: Infinity}; | |
} | |
// When the user selects an option, set the FIELD variable | |
// and update the colors and legend | |
d3.select('#selector') | |
.on('change', function() { | |
G.FIELD = this.value; | |
updateAll(); | |
}); | |
}); | |
} | |
function formatValue(value) { | |
// Formatting: | |
if (isNaN(value)) { ret = value; } // if not a number, no need to format | |
else if ( +value >= 1000 ) ret = F_COMMA(value); // Big numbers get commas | |
else if ( +value < 1000 ) ret = value.toFixed(2); // Small numbers get decimal places (but only 2) | |
return ret; | |
} | |
function drawColorChips() { | |
//var svg = "<svg width='24' height='270'>"; | |
var svg = "<svg width='24' height='"+numClasses*25+"'>"; | |
for ( var i = 0; i < numClasses; i++ ){ | |
svg += "<rect fill="+colorbrewer[selectedScheme][numClasses][i]+" width='24' height='"+Math.min(24,parseInt(265/numClasses))+"' y='"+i*Math.min(24,parseInt(265/numClasses))+"'/>"; | |
} | |
$("#color-chips").empty().append(svg); | |
updateChipValues(); | |
} | |
function updateChipValues() { | |
$("#color-values").empty(); | |
var str = ""; | |
$("#color-chips rect").each(function(i){ | |
chip_bounds = colorQuantile.invertExtent( colorbrewer[selectedScheme][numClasses][i] ); | |
str += formatValue(chip_bounds[0]) + ' – ' + formatValue(chip_bounds[1]); | |
str += "\n"; | |
}); | |
str = str.replace( /\n$/, "" ); | |
$("#color-values").append("<div class='textyarea' readonly style='line-height:"+Math.min(24,parseInt(265/numClasses))+"px; height:"+Math.min(24,parseInt(265/numClasses))*numClasses+"px'>"+str+"\n</div>"); | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment