Skip to content

Instantly share code, notes, and snippets.

@hotzeplotz
Created November 22, 2014 12:55
Show Gist options
  • Select an option

  • Save hotzeplotz/23780c778feb4dc4f50b to your computer and use it in GitHub Desktop.

Select an option

Save hotzeplotz/23780c778feb4dc4f50b to your computer and use it in GitHub Desktop.
Future of large cities - GDP // source http://jsbin.com/saqimiwosi
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Future of large cities - GDP</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox.js/v2.1.4/mapbox.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox.js/v2.1.4/mapbox.css' rel='stylesheet' />
<link href='style.css' rel='stylesheet' />
<style id="jsbin-css">
body {
margin:0;
padding:0;
font-family: sans-serif;
}
#map {
position:absolute;
top:0;
bottom:0;
width:100%;
}
.metro-tooltip{
position: absolute;
top: 2em;
right: 1em;
z-index: 6;
background: rgba(0,0,0,.75);
color: white;
padding: .5em .75em;
font-size: .85em;
display: none;
}
.xAxis path,
.xAxis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.ui-brush {
background: #f8f8f8;
position: absolute;
bottom: 0;
right: 0;
left: 0;
height: 140px;
display: none;
}
.brush .extent {
stroke: #fff;
fill-opacity: 0.125;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<!-- This example requires d3 for AJAX and the brush,
though you can bring your own library. -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.6/d3.min.js' charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='map' class='dark'></div>
<div class="metro-tooltip"></div>
<div id='brush' class='ui-brush'></div>
<script>
</script>
<script id="jsbin-javascript">
function make_map(map_data) {
L.mapbox.accessToken = 'pk.eyJ1IjoibHNlY2l0aWVzIiwiYSI6IjJ6ZjA5UGMifQ.8y1yiWucAic55wg5YoiTGQ';
var map = L.mapbox.map(map_data.map_id, 'lsecities.k37p4f29', {
attributionControl: false,
infoControl: true
})
.setView(map_data.initial_position, map_data.initial_zoom),
$metro_tooltip = $('.metro-tooltip');
// Credit our friendly open data producers.
/*map.infoControl.addInfo (
'<a href="http://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php">Data from USGS</a>');
*/
function enterLayer(){
var metroName = this.feature.properties.city_name.length > 0 ? this.feature.properties.city_name : this.feature.properties.city_name_alt,
val_2012 = map_data.time_var_value_function(this.feature.properties[map_data.time_var]),
val_growth = Math.round(this.feature.properties[map_data.growth_var] * 100) / 100;
$metro_tooltip.html(
'<h3>' + metroName + '</h3>' +
'<dl>' +
'<dt>' + map_data.time_var_label + '</dt>' +
'<dd>' + val_2012 + ' ' + map_data.time_var_unit + '</dd>' +
'<dt>' + map_data.growth_var_label + '</dt>' +
'<dd>' + val_growth + map_data.growth_var_unit + '</dd>' +
'</dl>'
).show();
this.bringToFront();
this.setStyle({
weight:2,
opacity: 1
});
}
function leaveLayer(){
$metro_tooltip.hide();
this.bringToBack();
this.setStyle({
weight:1,
opacity: 0.5
});
}
// Create a new layer with a special pointToLayer function
// that'll generate scaled points.
var citiesLayer = L.geoJson(null, {
pointToLayer: scaledPoint,
onEachFeature: function(feature, layer) {
layer.on({
mouseover: enterLayer,
mouseout: leaveLayer
});
}
})
.addTo(map);
var fill; // attach function within d3.json();
map.on('zoomend', function (e) {
zoomHandler();
});
function zoomHandler() {
citiesLayer.setStyle(citiesSize);
}
function citiesSize(feature) {
var currentZoom = map.getZoom();
return { radius: pointRadius(feature)};
}
function pointColor(feature) {
return fill(feature.properties[map_data.growth_var]);
}
function pointRadius(feature) {
return Math.sqrt(Math.sqrt(Math.round((feature.properties[map_data.time_var]) * 100) / 100) * map.getZoom());
}
function rectWidth(width, d, index) {
var x = d3.scale.linear()
.range([0, width])
.domain(map_data.time_var_extent);
return x(d.group_value);
}
function scaledPoint(feature, latlng) {
var val_2012 = Math.round((feature.properties[map_data.time_var] * map_data.time_var_multiplier) * 100) / 100,
val_gr = Math.round(feature.properties[map_data.growth_var] * 100) / 100;
return L.circleMarker(latlng, {
radius: pointRadius(feature),
fillColor: pointColor(feature),
fillOpacity: 0.7,
weight: 0.5,
color: '#fff'
});
}
// Request our data and add it to the earthquakesLayer.
d3.json(map_data.data_file, function(err, data) {
var min = d3.min(data.features.map(function(d) { return d.properties[map_data.growth_var]; })),
max = d3.max(data.features.map(function(d) { return d.properties[map_data.growth_var]; }));
/**
* add value of cumulative variable so far to each feature's properties
* sort by cumulative variable first or things won't make sense
*/
var val_so_far = 0; // keep track of pop so far in map below
var sorted_data_features = data.features.sort(function(a,b) { return b.properties[map_data.cumulative_var] - a.properties[map_data.cumulative_var]; }); // sort descending
var sorted_data_features_with_val_so_far = sorted_data_features.map(function(currentValue, index, array) {
currentValue.properties.val_so_far = +val_so_far;
val_so_far = +(currentValue.properties.val_so_far) + +(currentValue.properties[map_data.cumulative_var]);
return currentValue;
});
// once done, replace original features with features including the new variable with cumulative values
data.features = sorted_data_features_with_val_so_far;
/**
* Calculate group values for first 1, 5, 10, 20, 50, 100, 500 and 700 cities as per newspaper spread
*/
var group_data = [ 1, 5, 10, 20, 50, 100, 500, 700 ].map(function(d, i, array) {
var previous_groups_cumulative_total = (i > 0) ?
data.features[array[i - 1]].properties.val_so_far : 0;
return {
city_count: d,
group_value: data.features[d].properties.val_so_far - previous_groups_cumulative_total,
previous_groups_cumulative_total: previous_groups_cumulative_total
};
});
fill = d3.scale.threshold()
.domain([ -1 , 0, 1, 2, 3, 4, 5 ])
.range([ "#b3c8eb", "#d3dff4", "#f8d7db", "#eea9b5", "#e48391", "#de5f74", "#d73f5b", "#d22247"]);
citiesLayer.addData(data);
if(true === map_data.brush) {
setBrush(data, group_data);
}
});
function setBrush(data, group_data) {
var container = d3.select('#brush'),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = container.node().offsetWidth - margin.left - margin.right,
height = 80;
var val_extent = map_data.time_var_extent; // todo: use actual world population figure from printed version
var svg = container.append('svg')
.attr('width', width )
.attr('height', height + margin.top + margin.bottom);
var context = svg.append('g')
.attr('class', 'context')
.attr('transform', 'translate(' +
margin.left + ',' +
margin.top + ')');
var x = d3.scale.linear()
.range([0, width])
.domain(val_extent);
var xPercent = d3.scale.linear()
.range([0, width])
.domain([0,1]);
var xAxis = d3.svg.axis()
.scale(xPercent)
.tickFormat(d3.format('%')).orient('bottom');
var xAxisGroup = context.append("g")
.attr('transform', 'translate(0, ' + (height) + ')')
.attr('class', 'xAxis')
.call(xAxis);
var brush = d3.svg.brush()
.x(x)
.on('brushend', brushend);
context.selectAll('rect.city')
.data(group_data)
.enter()
.append('rect')
.attr('transform', function(d) {
return 'translate(' + [x(d.previous_groups_cumulative_total), 0] + ')';
})
.attr('width', rectWidth.bind(null, width))
.attr('height', height * 0.7)
.attr('data-cumulative-count', function(d, i) {
return d.city_count;
})
.attr('opacity', 1)
.attr('fill', function(d, i) {
var base_color = d3.rgb('#d22247');
return base_color.brighter(0.5 * i);
});
context.selectAll('text.city-count')
.data(group_data)
.enter()
.append('text')
.attr('class', 'city-count')
.attr('x', function(d, i) { return x(d.previous_groups_cumulative_total); })
.attr('y', '-2')
.text(function(d, i) { return d.city_count; });
context.append('g')
.attr('class', 'x brush')
.call(brush)
.selectAll('rect')
.attr('y', -6)
.attr('height', height);
function brushend() {
var filter;
// If the user has selected no brush area, share everything.
if (brush.empty()) {
filter = function() { return true; };
} else {
// Otherwise, restrict features to only things in the brush extent.
filter = function(feature) {
return feature.properties.val_so_far > +brush.extent()[0] &&
((+feature.properties.val_so_far) + (+feature.properties[map_data.cumulative_var])) < (+brush.extent()[1]);
};
}
var filtered = data.features.filter(filter);
citiesLayer.clearLayers()
.addData(filtered);
}
}
}
make_map({
data_file: 'http://jsbin.com/maqidohina.json',
map_id: 'map',
initial_position: [0,0],
initial_zoom: 2,
brush: false,
time_var: 'gdp_pcapit',
growth_var: 'gdp_growth',
cumulative_var: 'gdp_2012',
time_var_label: 'GDP Per capita (2012)',
time_var_unit: 'US$',
time_var_extent: [0, 72600074.2624515], // use actual gloabl GDP figure used for newspaper calculations
time_var_value_function: function(val) {
return Math.round(val * 1000);
},
growth_var_label: 'Average annual GDP growth forecast 2012&mdash;2030',
growth_var_unit: '%'
});
</script>
<script id="jsbin-source-html" type="text/html"><!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Future of large cities - GDP</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox.js/v2.1.4/mapbox.js'><\/script>
<link href='https://api.tiles.mapbox.com/mapbox.js/v2.1.4/mapbox.css' rel='stylesheet' />
<link href='style.css' rel='stylesheet' />
</head>
<body>
<\!-- This example requires d3 for AJAX and the brush,
though you can bring your own library. -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.6/d3.min.js' charset="utf-8"><\/script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"><\/script>
<div id='map' class='dark'></div>
<div class="metro-tooltip"></div>
<div id='brush' class='ui-brush'></div>
<script>
<\/script>
</body>
</html> </script>
<script id="jsbin-source-css" type="text/css">body {
margin:0;
padding:0;
font-family: sans-serif;
}
#map {
position:absolute;
top:0;
bottom:0;
width:100%;
}
.metro-tooltip{
position: absolute;
top: 2em;
right: 1em;
z-index: 6;
background: rgba(0,0,0,.75);
color: white;
padding: .5em .75em;
font-size: .85em;
display: none;
}
.xAxis path,
.xAxis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.ui-brush {
background: #f8f8f8;
position: absolute;
bottom: 0;
right: 0;
left: 0;
height: 140px;
display: none;
}
.brush .extent {
stroke: #fff;
fill-opacity: 0.125;
shape-rendering: crispEdges;
} </script>
<script id="jsbin-source-javascript" type="text/javascript">function make_map(map_data) {
L.mapbox.accessToken = 'pk.eyJ1IjoibHNlY2l0aWVzIiwiYSI6IjJ6ZjA5UGMifQ.8y1yiWucAic55wg5YoiTGQ';
var map = L.mapbox.map(map_data.map_id, 'lsecities.k37p4f29', {
attributionControl: false,
infoControl: true
})
.setView(map_data.initial_position, map_data.initial_zoom),
$metro_tooltip = $('.metro-tooltip');
// Credit our friendly open data producers.
/*map.infoControl.addInfo (
'<a href="http://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php">Data from USGS</a>');
*/
function enterLayer(){
var metroName = this.feature.properties.city_name.length > 0 ? this.feature.properties.city_name : this.feature.properties.city_name_alt,
val_2012 = map_data.time_var_value_function(this.feature.properties[map_data.time_var]),
val_growth = Math.round(this.feature.properties[map_data.growth_var] * 100) / 100;
$metro_tooltip.html(
'<h3>' + metroName + '</h3>' +
'<dl>' +
'<dt>' + map_data.time_var_label + '</dt>' +
'<dd>' + val_2012 + ' ' + map_data.time_var_unit + '</dd>' +
'<dt>' + map_data.growth_var_label + '</dt>' +
'<dd>' + val_growth + map_data.growth_var_unit + '</dd>' +
'</dl>'
).show();
this.bringToFront();
this.setStyle({
weight:2,
opacity: 1
});
}
function leaveLayer(){
$metro_tooltip.hide();
this.bringToBack();
this.setStyle({
weight:1,
opacity: 0.5
});
}
// Create a new layer with a special pointToLayer function
// that'll generate scaled points.
var citiesLayer = L.geoJson(null, {
pointToLayer: scaledPoint,
onEachFeature: function(feature, layer) {
layer.on({
mouseover: enterLayer,
mouseout: leaveLayer
});
}
})
.addTo(map);
var fill; // attach function within d3.json();
map.on('zoomend', function (e) {
zoomHandler();
});
function zoomHandler() {
citiesLayer.setStyle(citiesSize);
}
function citiesSize(feature) {
var currentZoom = map.getZoom();
return { radius: pointRadius(feature)};
}
function pointColor(feature) {
return fill(feature.properties[map_data.growth_var]);
}
function pointRadius(feature) {
return Math.sqrt(Math.sqrt(Math.round((feature.properties[map_data.time_var]) * 100) / 100) * map.getZoom());
}
function rectWidth(width, d, index) {
var x = d3.scale.linear()
.range([0, width])
.domain(map_data.time_var_extent);
return x(d.group_value);
}
function scaledPoint(feature, latlng) {
var val_2012 = Math.round((feature.properties[map_data.time_var] * map_data.time_var_multiplier) * 100) / 100,
val_gr = Math.round(feature.properties[map_data.growth_var] * 100) / 100;
return L.circleMarker(latlng, {
radius: pointRadius(feature),
fillColor: pointColor(feature),
fillOpacity: 0.7,
weight: 0.5,
color: '#fff'
});
}
// Request our data and add it to the earthquakesLayer.
d3.json(map_data.data_file, function(err, data) {
var min = d3.min(data.features.map(function(d) { return d.properties[map_data.growth_var]; })),
max = d3.max(data.features.map(function(d) { return d.properties[map_data.growth_var]; }));
/**
* add value of cumulative variable so far to each feature's properties
* sort by cumulative variable first or things won't make sense
*/
var val_so_far = 0; // keep track of pop so far in map below
var sorted_data_features = data.features.sort(function(a,b) { return b.properties[map_data.cumulative_var] - a.properties[map_data.cumulative_var]; }); // sort descending
var sorted_data_features_with_val_so_far = sorted_data_features.map(function(currentValue, index, array) {
currentValue.properties.val_so_far = +val_so_far;
val_so_far = +(currentValue.properties.val_so_far) + +(currentValue.properties[map_data.cumulative_var]);
return currentValue;
});
// once done, replace original features with features including the new variable with cumulative values
data.features = sorted_data_features_with_val_so_far;
/**
* Calculate group values for first 1, 5, 10, 20, 50, 100, 500 and 700 cities as per newspaper spread
*/
var group_data = [ 1, 5, 10, 20, 50, 100, 500, 700 ].map(function(d, i, array) {
var previous_groups_cumulative_total = (i > 0) ?
data.features[array[i - 1]].properties.val_so_far : 0;
return {
city_count: d,
group_value: data.features[d].properties.val_so_far - previous_groups_cumulative_total,
previous_groups_cumulative_total: previous_groups_cumulative_total
};
});
fill = d3.scale.threshold()
.domain([ -1 , 0, 1, 2, 3, 4, 5 ])
.range([ "#b3c8eb", "#d3dff4", "#f8d7db", "#eea9b5", "#e48391", "#de5f74", "#d73f5b", "#d22247"]);
citiesLayer.addData(data);
if(true === map_data.brush) {
setBrush(data, group_data);
}
});
function setBrush(data, group_data) {
var container = d3.select('#brush'),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = container.node().offsetWidth - margin.left - margin.right,
height = 80;
var val_extent = map_data.time_var_extent; // todo: use actual world population figure from printed version
var svg = container.append('svg')
.attr('width', width )
.attr('height', height + margin.top + margin.bottom);
var context = svg.append('g')
.attr('class', 'context')
.attr('transform', 'translate(' +
margin.left + ',' +
margin.top + ')');
var x = d3.scale.linear()
.range([0, width])
.domain(val_extent);
var xPercent = d3.scale.linear()
.range([0, width])
.domain([0,1]);
var xAxis = d3.svg.axis()
.scale(xPercent)
.tickFormat(d3.format('%')).orient('bottom');
var xAxisGroup = context.append("g")
.attr('transform', 'translate(0, ' + (height) + ')')
.attr('class', 'xAxis')
.call(xAxis);
var brush = d3.svg.brush()
.x(x)
.on('brushend', brushend);
context.selectAll('rect.city')
.data(group_data)
.enter()
.append('rect')
.attr('transform', function(d) {
return 'translate(' + [x(d.previous_groups_cumulative_total), 0] + ')';
})
.attr('width', rectWidth.bind(null, width))
.attr('height', height * 0.7)
.attr('data-cumulative-count', function(d, i) {
return d.city_count;
})
.attr('opacity', 1)
.attr('fill', function(d, i) {
var base_color = d3.rgb('#d22247');
return base_color.brighter(0.5 * i);
});
context.selectAll('text.city-count')
.data(group_data)
.enter()
.append('text')
.attr('class', 'city-count')
.attr('x', function(d, i) { return x(d.previous_groups_cumulative_total); })
.attr('y', '-2')
.text(function(d, i) { return d.city_count; });
context.append('g')
.attr('class', 'x brush')
.call(brush)
.selectAll('rect')
.attr('y', -6)
.attr('height', height);
function brushend() {
var filter;
// If the user has selected no brush area, share everything.
if (brush.empty()) {
filter = function() { return true; };
} else {
// Otherwise, restrict features to only things in the brush extent.
filter = function(feature) {
return feature.properties.val_so_far > +brush.extent()[0] &&
((+feature.properties.val_so_far) + (+feature.properties[map_data.cumulative_var])) < (+brush.extent()[1]);
};
}
var filtered = data.features.filter(filter);
citiesLayer.clearLayers()
.addData(filtered);
}
}
}
make_map({
data_file: 'http://jsbin.com/maqidohina.json',
map_id: 'map',
initial_position: [0,0],
initial_zoom: 2,
brush: false,
time_var: 'gdp_pcapit',
growth_var: 'gdp_growth',
cumulative_var: 'gdp_2012',
time_var_label: 'GDP Per capita (2012)',
time_var_unit: 'US$',
time_var_extent: [0, 72600074.2624515], // use actual gloabl GDP figure used for newspaper calculations
time_var_value_function: function(val) {
return Math.round(val * 1000);
},
growth_var_label: 'Average annual GDP growth forecast 2012&mdash;2030',
growth_var_unit: '%'
});</script></body>
</html>
body {
margin:0;
padding:0;
font-family: sans-serif;
}
#map {
position:absolute;
top:0;
bottom:0;
width:100%;
}
.metro-tooltip{
position: absolute;
top: 2em;
right: 1em;
z-index: 6;
background: rgba(0,0,0,.75);
color: white;
padding: .5em .75em;
font-size: .85em;
display: none;
}
.xAxis path,
.xAxis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.ui-brush {
background: #f8f8f8;
position: absolute;
bottom: 0;
right: 0;
left: 0;
height: 140px;
display: none;
}
.brush .extent {
stroke: #fff;
fill-opacity: 0.125;
shape-rendering: crispEdges;
}
function make_map(map_data) {
L.mapbox.accessToken = 'pk.eyJ1IjoibHNlY2l0aWVzIiwiYSI6IjJ6ZjA5UGMifQ.8y1yiWucAic55wg5YoiTGQ';
var map = L.mapbox.map(map_data.map_id, 'lsecities.k37p4f29', {
attributionControl: false,
infoControl: true
})
.setView(map_data.initial_position, map_data.initial_zoom),
$metro_tooltip = $('.metro-tooltip');
// Credit our friendly open data producers.
/*map.infoControl.addInfo (
'<a href="http://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php">Data from USGS</a>');
*/
function enterLayer(){
var metroName = this.feature.properties.city_name.length > 0 ? this.feature.properties.city_name : this.feature.properties.city_name_alt,
val_2012 = map_data.time_var_value_function(this.feature.properties[map_data.time_var]),
val_growth = Math.round(this.feature.properties[map_data.growth_var] * 100) / 100;
$metro_tooltip.html(
'<h3>' + metroName + '</h3>' +
'<dl>' +
'<dt>' + map_data.time_var_label + '</dt>' +
'<dd>' + val_2012 + ' ' + map_data.time_var_unit + '</dd>' +
'<dt>' + map_data.growth_var_label + '</dt>' +
'<dd>' + val_growth + map_data.growth_var_unit + '</dd>' +
'</dl>'
).show();
this.bringToFront();
this.setStyle({
weight:2,
opacity: 1
});
}
function leaveLayer(){
$metro_tooltip.hide();
this.bringToBack();
this.setStyle({
weight:1,
opacity: 0.5
});
}
// Create a new layer with a special pointToLayer function
// that'll generate scaled points.
var citiesLayer = L.geoJson(null, {
pointToLayer: scaledPoint,
onEachFeature: function(feature, layer) {
layer.on({
mouseover: enterLayer,
mouseout: leaveLayer
});
}
})
.addTo(map);
var fill; // attach function within d3.json();
map.on('zoomend', function (e) {
zoomHandler();
});
function zoomHandler() {
citiesLayer.setStyle(citiesSize);
}
function citiesSize(feature) {
var currentZoom = map.getZoom();
return { radius: pointRadius(feature)};
}
function pointColor(feature) {
return fill(feature.properties[map_data.growth_var]);
}
function pointRadius(feature) {
return Math.sqrt(Math.sqrt(Math.round((feature.properties[map_data.time_var]) * 100) / 100) * map.getZoom());
}
function rectWidth(width, d, index) {
var x = d3.scale.linear()
.range([0, width])
.domain(map_data.time_var_extent);
return x(d.group_value);
}
function scaledPoint(feature, latlng) {
var val_2012 = Math.round((feature.properties[map_data.time_var] * map_data.time_var_multiplier) * 100) / 100,
val_gr = Math.round(feature.properties[map_data.growth_var] * 100) / 100;
return L.circleMarker(latlng, {
radius: pointRadius(feature),
fillColor: pointColor(feature),
fillOpacity: 0.7,
weight: 0.5,
color: '#fff'
});
}
// Request our data and add it to the earthquakesLayer.
d3.json(map_data.data_file, function(err, data) {
var min = d3.min(data.features.map(function(d) { return d.properties[map_data.growth_var]; })),
max = d3.max(data.features.map(function(d) { return d.properties[map_data.growth_var]; }));
/**
* add value of cumulative variable so far to each feature's properties
* sort by cumulative variable first or things won't make sense
*/
var val_so_far = 0; // keep track of pop so far in map below
var sorted_data_features = data.features.sort(function(a,b) { return b.properties[map_data.cumulative_var] - a.properties[map_data.cumulative_var]; }); // sort descending
var sorted_data_features_with_val_so_far = sorted_data_features.map(function(currentValue, index, array) {
currentValue.properties.val_so_far = +val_so_far;
val_so_far = +(currentValue.properties.val_so_far) + +(currentValue.properties[map_data.cumulative_var]);
return currentValue;
});
// once done, replace original features with features including the new variable with cumulative values
data.features = sorted_data_features_with_val_so_far;
/**
* Calculate group values for first 1, 5, 10, 20, 50, 100, 500 and 700 cities as per newspaper spread
*/
var group_data = [ 1, 5, 10, 20, 50, 100, 500, 700 ].map(function(d, i, array) {
var previous_groups_cumulative_total = (i > 0) ?
data.features[array[i - 1]].properties.val_so_far : 0;
return {
city_count: d,
group_value: data.features[d].properties.val_so_far - previous_groups_cumulative_total,
previous_groups_cumulative_total: previous_groups_cumulative_total
};
});
fill = d3.scale.threshold()
.domain([ -1 , 0, 1, 2, 3, 4, 5 ])
.range([ "#b3c8eb", "#d3dff4", "#f8d7db", "#eea9b5", "#e48391", "#de5f74", "#d73f5b", "#d22247"]);
citiesLayer.addData(data);
if(true === map_data.brush) {
setBrush(data, group_data);
}
});
function setBrush(data, group_data) {
var container = d3.select('#brush'),
margin = {top: 20, right: 20, bottom: 20, left: 20},
width = container.node().offsetWidth - margin.left - margin.right,
height = 80;
var val_extent = map_data.time_var_extent; // todo: use actual world population figure from printed version
var svg = container.append('svg')
.attr('width', width )
.attr('height', height + margin.top + margin.bottom);
var context = svg.append('g')
.attr('class', 'context')
.attr('transform', 'translate(' +
margin.left + ',' +
margin.top + ')');
var x = d3.scale.linear()
.range([0, width])
.domain(val_extent);
var xPercent = d3.scale.linear()
.range([0, width])
.domain([0,1]);
var xAxis = d3.svg.axis()
.scale(xPercent)
.tickFormat(d3.format('%')).orient('bottom');
var xAxisGroup = context.append("g")
.attr('transform', 'translate(0, ' + (height) + ')')
.attr('class', 'xAxis')
.call(xAxis);
var brush = d3.svg.brush()
.x(x)
.on('brushend', brushend);
context.selectAll('rect.city')
.data(group_data)
.enter()
.append('rect')
.attr('transform', function(d) {
return 'translate(' + [x(d.previous_groups_cumulative_total), 0] + ')';
})
.attr('width', rectWidth.bind(null, width))
.attr('height', height * 0.7)
.attr('data-cumulative-count', function(d, i) {
return d.city_count;
})
.attr('opacity', 1)
.attr('fill', function(d, i) {
var base_color = d3.rgb('#d22247');
return base_color.brighter(0.5 * i);
});
context.selectAll('text.city-count')
.data(group_data)
.enter()
.append('text')
.attr('class', 'city-count')
.attr('x', function(d, i) { return x(d.previous_groups_cumulative_total); })
.attr('y', '-2')
.text(function(d, i) { return d.city_count; });
context.append('g')
.attr('class', 'x brush')
.call(brush)
.selectAll('rect')
.attr('y', -6)
.attr('height', height);
function brushend() {
var filter;
// If the user has selected no brush area, share everything.
if (brush.empty()) {
filter = function() { return true; };
} else {
// Otherwise, restrict features to only things in the brush extent.
filter = function(feature) {
return feature.properties.val_so_far > +brush.extent()[0] &&
((+feature.properties.val_so_far) + (+feature.properties[map_data.cumulative_var])) < (+brush.extent()[1]);
};
}
var filtered = data.features.filter(filter);
citiesLayer.clearLayers()
.addData(filtered);
}
}
}
make_map({
data_file: 'http://jsbin.com/maqidohina.json',
map_id: 'map',
initial_position: [0,0],
initial_zoom: 2,
brush: false,
time_var: 'gdp_pcapit',
growth_var: 'gdp_growth',
cumulative_var: 'gdp_2012',
time_var_label: 'GDP Per capita (2012)',
time_var_unit: 'US$',
time_var_extent: [0, 72600074.2624515], // use actual gloabl GDP figure used for newspaper calculations
time_var_value_function: function(val) {
return Math.round(val * 1000);
},
growth_var_label: 'Average annual GDP growth forecast 2012&mdash;2030',
growth_var_unit: '%'
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment