Created
November 22, 2014 12:55
-
-
Save hotzeplotz/23780c778feb4dc4f50b to your computer and use it in GitHub Desktop.
Future of large cities - GDP // source http://jsbin.com/saqimiwosi
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 /> | |
| <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—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—2030', | |
| growth_var_unit: '%' | |
| });</script></body> | |
| </html> |
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
| 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; | |
| } |
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
| 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—2030', | |
| growth_var_unit: '%' | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment