Created
June 18, 2015 23:02
-
-
Save jhnklly/32588f7da3806f152c20 to your computer and use it in GitHub Desktop.
external data joins--test of concept
This file contains 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 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 home = [ // we'll set the map extent to these bounds (nyc) | |
{lon: -74, lat: 40.68}, | |
{lon: -73.7, lat: 40.83} | |
]; | |
var home = [ // we'll set the map extent to these bounds (sf) | |
{lon: -122.52, lat: 37.70}, | |
{lon: -122.35, lat: 37.81} | |
]; | |
var G = {}; // A global object to store the field variables | |
G.field_stats = {}; | |
G.FIELD = "94110"; | |
G.fields_arr = []; | |
G.zips = { | |
'94110': { | |
'94104': 0.1, | |
'94124': 0.15, | |
'94121': 0.2, | |
'94122': 0.25, | |
'94116': 0.3, | |
}, | |
'94104': { | |
'94110': 0.7, | |
'94124': 0.8, | |
'94121': 0.9, | |
'94122': 0.95, | |
'94116': 0.99, | |
}, | |
'94116': { | |
'94110': 0.2, | |
'94124': 0.4, | |
'94121': 0.5, | |
'94122': 0.6, | |
'94104': 0.9, | |
}, | |
'94121': { | |
'94110': 0.2, | |
'94124': 0.4, | |
'94104': 0.5, | |
'94122': 0.6, | |
'94116': 0.9, | |
}, | |
'94124': { | |
'94104': 0.2, | |
'94110': 0.4, | |
'94121': 0.5, | |
'94122': 0.6, | |
'94116': 0.9, | |
} | |
}; | |
G.zips_arr = []; | |
for (var key in G.zips) { | |
G.zips_arr.push(key); | |
} | |
var colorQuantile, | |
schema_url, | |
demographics_url, | |
demographics_layer, | |
selectedScheme = 'Blues', | |
numClasses = 9, | |
F_COMMA = d3.format("0,000"); | |
// 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 += "&select=layer,fips_code,zip_code"; | |
demographics_url += "&where=name!='Puerto Rico' AND layer=='zip_code'"; | |
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 joinData() { | |
d3.selectAll('.ms') | |
.data(G.zips, function(d) { | |
console.log( d.properties.fips_code, G.zips[d.properties.fips_code] ); | |
return G.zips[d.properties.fips_code]; | |
}); | |
} | |
function initMap() { | |
var param_selection_function = (function(whitelist) { | |
return function(s) { | |
applyColors(); // Update the colors based on the current global FIELD | |
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(home); | |
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) { | |
d3.selectAll('.ms') | |
.style("fill", function(d,i) { | |
colorQuantile = d3.scale.quantile() | |
.domain([0,1]) | |
.range(colorbrewer[selectedScheme][numClasses]) | |
; | |
var colr = '#bbb'; | |
if (d.properties.fips_code) { | |
// the polygon's own zip: | |
//console.log(d); | |
var own_zip = d.properties.fips_code; | |
if (G.zips[own_zip] && own_zip !== G.FIELD) { | |
var value_from_selected_zip = G.zips[own_zip][G.FIELD]; | |
console.log(G.FIELD, own_zip, value_from_selected_zip, colorQuantile(value_from_selected_zip)); | |
colr = colorQuantile(value_from_selected_zip); | |
} else if (own_zip == G.FIELD) { | |
console.log(G.FIELD, own_zip, 'magenta'); | |
colr = '#ff00ff'; | |
} | |
return colr; | |
} | |
}); | |
} | |
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... | |
.data(G.zips_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