79ºF

Modern-day redlining interactive map


 

 

 

 

 

Modern-Day Redlining

In 61 metro areas across the U.S., people of color were more likely to be denied conventional mortgage loans than whites, even when controlling for applicants' income, loan amount and neighborhood.

This map of a statistical analysis by Reveal from The Center for Investigative Reporting tracks those disparities.

To get started, click on a metro area or search for an address.

Read Kept Out, Reveal’s full investigation, here.

Sources

Reveal analysis of Home Mortgage Disclosure Act data, U.S. Census Bureau

Credits

Design and web development: Allison McCartney and Michael Corey

Data analysis: Emmanuel Martinez, Aaron Glantz and Angeliki Kastanis

Edited by Amy Pyle and copy edited by Nadia Wynter and Nikki Frick

Loading

 

 

 

 

 

 

 

Metro areas with lending
disparities, by race

Likelihood of being denied
compared to white applicant

 

 

 

// Google analytics window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-2147301-25'); new pym.Child({ polling: 500 }); // Check if user has parameter in URL, return that parameter var getParameter = (param) => { var params = window.location.search.substr(1).split('&'); for (var i = 0; i < params.length; i++) { var p=params[i].split('='); if (p[0] == param) { return decodeURIComponent(p[1]); } } return false; }; // Removes popup if ?title=false parameter is in URL if (getParameter('title') == 'false') { // d3.select('#titles-toggle').remove(); } else { $(window).on('load', () => { $('#basicModal').modal('show'); }); } var colorScale; if (getParameter("design") == "ap") { // for AP d3.select("#stylesheet").attr("href", "https://apps.revealnews.org/redlining/css/main-ap.css"); var colorScale = [ [0, "#808080"], [1.5, "#808080"], [1.501, '#EDB1A6'], [2, '#EDB1A6'], [2.01, '#D8755E'], [3, '#D8755E'], [3.01, '#C83728'], [4, '#C83728'], [4.01, '#A52722'], [5, '#A52722'], [5.01, '#831618'], [6, '#831618'] ]; // Send event for AP views gtag('event', 'View type', { 'event_category': 'Redlining', 'event_label': 'AP view', 'value': 0 }); } else { // for CIR var colorScale = [ [0, "#808080"], [1.5, "#808080"], [1.501, '#F6D8D5'], [2, '#F6D8D5'], [2.01, '#EDB2AB'], [3, '#EDB2AB'], [3.01, '#E38B82'], [4, '#E38B82'], [4.01, '#DA6558'], [5, '#DA6558'], [5.01, '#D24435'], [6, '#D24435'] ]; } var root_url = 'https://redlining.apps.revealnews.org/' // zoom level at which we cross from metro to census tract const rubicon = 9; const USBounds = [[-124.848974, 24.396308], [-66.885444, 49.384358]]; // Set default padding based on screen width var setPadding = () => { var w = window.innerWidth; if (w < 480) { return { top: 100, bottom: 200, left: 5, right: 5 }} else if (w < 650) { return 15 } else if (w < 992) { return 30 } else { return 50 }; } mapboxgl.accessToken = 'pk.eyJ1IjoiY2lyIiwiYSI6IkExZEYwbDAifQ.-7tZFOTs1wPUxc1VXejBlg'; var map = new mapboxgl.Map({ container: 'map', // container id maxBounds: [[-200.848974, -30], [-20.885444, 80.384358]], style: 'mapbox://styles/cir/cjcxp0lnv1c422smcbw7l3cuw' //stylesheet location }); // Map controls map.addControl(new mapboxgl.NavigationControl()); map.scrollZoom.disable(); var race_dict = { "black": "black_odds", "latino": "latino_odd", "asian": "asian_odds", "native": "native_ame" }; // Add choropleth layer map.on('load', () => { map.fitBounds(USBounds, { padding: setPadding() }); var firstLineID = getFirstLineLayer(map); var race_property = setRaceURL(); // Add MSA layer map.addLayer({ "id": "metro-data", "source": { type: 'vector', url: 'mapbox://cir.dq8qftym' }, "source-layer": "metro_area_data_refactored-duzy81", "type": "fill", 'maxzoom': rubicon, "paint": { 'fill-color': { type: 'interval', property: race_property, stops: colorScale, default: "rgb(63,66,78)" }, 'fill-opacity': .7, 'fill-outline-color': 'rgba(255,255,255,.08)' } }, firstLineID); // Add census tract geometry map.addLayer({ "id": "census-tracts", "source": { type: 'vector', url: 'mapbox://cir.0m0aai4t' }, "source-layer": "tracts", "type": "fill", 'minzoom': rubicon, "paint": { 'fill-color': 'rgb(78, 84, 90)', 'fill-opacity': 0.3 } }, firstLineID); // Add census tract outlines map.addLayer({ "id": "census-tracts-outline", "source": { type: 'vector', url: 'mapbox://cir.0m0aai4t' }, "source-layer": "tracts", "type": "line", 'minzoom': rubicon, "paint": { 'line-color': 'white', 'line-opacity': .2, 'line-width': .5 } }); if (hasTouch()) { } else { setTooltips(map); } if (getParameter("metro_area")) { geocodeAddressMSA(getParameter("metro_area")); } else if (getParameter("address")) { geocodeAddress(getParameter("address")); } else if (getParameter("tract")) { geocodeAddressTract(getParameter("tract")); }; }); var setTooltips = (map) => { var tooltip = d3.select("#tooltip"); var toolpnt = d3.select("#tooltip-point"); map.on('mousemove', (e) => { var zoom = map.getZoom(); if (zoom < rubicon ) { var features = map.queryRenderedFeatures(e.point, {layers:['metro-data']});} else { var features = map.queryRenderedFeatures(e.point, {layers:['census-tracts']});} if (features.length) { if (zoom < rubicon ) { var name = formatMetroArea(features[0].properties.name); } else { var name = features[0].properties.tract_name; } tooltip.text(name); var height = parseInt(tooltip.style("height")); if (d3.select("#overlay").classed("active")) { tooltip.attr("style", "top: " + (e.point.y + 15 - height) + "px; left: " + (e.point.x + 35) + "px; visibility: visible;"); toolpnt.attr("style", "top: " + (e.point.y + 10) + "px; left: " + (e.point.x + 35) + "px; visibility: visible;"); } else { tooltip.attr("style", "top: " + (e.point.y - 15 - height) + "px; left: " + e.point.x + "px; visibility: visible;"); toolpnt.attr("style", "top: " + (e.point.y - 20) + "px; left: " + e.point.x + "px; visibility: visible;"); } map.getCanvas().style.cursor = 'pointer'; } else { tooltip.attr('style', "visibility: hidden;"); toolpnt.attr('style', "visibility: hidden;"); map.getCanvas().style.cursor = 'grab'; } }); // Make sure tooltips disappear when the mouse leaves the map map.on('mouseleave', 'metro-data', (e) => { map.getCanvas().style.cursor = ''; tooltip.attr('style', "visibility: hidden;"); toolpnt.attr('style', "visibility: hidden;"); }); map.on('mouseleave', 'census-tracts', (e) => { map.getCanvas().style.cursor = ''; tooltip.attr('style', "visibility: hidden;"); toolpnt.attr('style', "visibility: hidden;"); }); }; var getFirstLineLayer = (map) => { var layers = map.getStyle().layers; // Find the index of the first line layer in the map style for (var i = 0; i < layers.length; i++) { if (layers[i].type === 'line') { return layers[i].id; break; } } }; var getFirstSymbolLayer = (map) => { var layers = map.getStyle().layers; // Find the index of the first symbol layer in the map style for (var i = 0; i < layers.length; i++) { if (layers[i].type === 'symbol') { return layers[i].id; break; } } } var updateURL = (param, value) => { var base_url = window.location.protocol + "//" + window.location.host + window.location.pathname; if (getParameter("design") == "ap") { var design = "ap"; } else { var design = "cir"; } if (param == "race_ethn") { var race_ethn = value; } else { if (getParameter("race_ethn")) { var race_ethn = getParameter("race_ethn")} else { var race_ethn = "black" }; } params = { 'design': design, 'race_ethn': race_ethn }; var str = "?" + $.param(params); if (param && param != 'race_ethn') { if (history.pushState) { var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + str + '&' + param + '=' + value; window.history.pushState({path:newurl},'',newurl); } } else { if (history.pushState) { var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + str; window.history.pushState({path:newurl},'',newurl); } } }; var setRaceURL = () => { if (getParameter("race_ethn")) { var race_ethn = getParameter("race_ethn"); var race_property = race_dict[race_ethn]; d3.select("#switch_race_" + race_ethn).attr('checked', 'checked'); return race_property; } else { var race_property = 'black_odds'; return race_property; }; }; d3.selectAll("#race-form input").on("change", function() { map.removeLayer("metro-data"); map.removeSource("metro-data"); var firstLineID = getFirstLineLayer(map); map.addLayer({ "id": "metro-data", "source": { type: 'vector', url: 'mapbox://cir.dq8qftym' }, "source-layer": "metro_area_data_refactored-duzy81", "type": "fill", 'maxzoom': rubicon, "paint": { 'fill-color': { property: race_dict[this.value], stops: colorScale, default: "rgb(63,66,78)" }, 'fill-opacity': .7, 'fill-outline-color': 'rgba(255,255,255,.08)' } }, firstLineID); map.fitBounds(USBounds, { padding: setPadding() }); updateURL('race_ethn', this.value) }); // reset button d3.select(".mapboxgl-ctrl-top-right") .append('div').classed('mapboxgl-ctrl mapboxgl-ctrl-group', true) .append('button').attr('id', 'reset-map').classed('mb-style', true) .on('click', () => { map.fitBounds(USBounds, { padding: setPadding() }); }) .append('span') .classed('glyphicon glyphicon-refresh', true); // // // Click open infobox // // map.on('click', (e) => { var zoom = map.getZoom(); // If about zoom level nine, zoom to metro data if (zoom < rubicon ) { var features = map.queryRenderedFeatures(e.point, {layers:['metro-data']}); if (features.length > 0) { addLoadingImg(); removeUrbanOutline(); removeTractOutline(); removeSinglePoint(); updateURL("metro_area", features[0].properties.msa_fips); geocodeAddressMSA(features[0].properties.msa_fips); gtag('event', 'Metro click', { 'event_category': 'Redlining', 'event_label': features[0].properties.msa_fips, 'value': 0 }); }; } // If below, zoom to census tract else { var features = map.queryRenderedFeatures(e.point, {layers:['census-tracts']}); console.log(features); if (features.length > 0) { addLoadingImg(); removeUrbanOutline(); removeTractOutline(); removeSinglePoint(); updateURL("tract", features[0].properties.geoid); geocodeAddressTract(features[0].properties.geoid); console.log(features[0].properties); gtag('event', 'Tract click', { 'event_category': 'Redlining', 'event_label': features[0].properties.geoid, 'value': 0 }); } } }); // // // Search box // // var addLoadingImg = () => { d3.select("#loading").classed('in-progress', true); }; var removeUrbanOutline = () => { if (map.getLayer('urban-areas-fill')) { map.removeLayer('urban-areas-fill'); map.removeSource('urban-areas-fill'); } }; var removeTractOutline = () => { if (map.getLayer('tract-areas-fill')) { map.removeLayer('tract-areas-fill'); map.removeSource('tract-areas-fill'); } }; var removeSinglePoint = () => { if (map.getLayer('single-point')) { map.removeLayer('single-point'); map.removeSource('single-point'); } }; function processForm(e) { if (e.preventDefault) e.preventDefault(); var address = document.getElementById('location-search').value; removeUrbanOutline(); removeTractOutline(); removeSinglePoint(); updateURL("address", address); geocodeAddress(address); // Returning false to prevent standard form behavior return false; } // Handling the form submit var searchForm = document.getElementById('search-form'); if (searchForm.attachEvent) { searchForm.attachEvent("submit", processForm); } else { searchForm.addEventListener("submit", processForm); } var mapCanvas = d3.select('.mapboxgl-canvas'); var mapDiv = d3.select('#map'); var geocodeAddressTract = (geoid) => { addLoadingImg(); var base_url = root_url + 'geocoder/?tract_geoid=' fetch(base_url + geoid) // Call the fetch function passing the url of the API as a parameter .then((resp) => resp.json()) .then((data) => { console.log(data); var layers = map.getStyle().layers; var firstSymbolId = getFirstSymbolLayer(map); var tractBounds = turf.extent(data['tract_data']['geometry']); map.addLayer({ 'id': 'urban-areas-fill', 'type': 'line', 'source': { 'type': 'geojson', 'data': data['metro_data'] }, 'paint': { 'line-color': 'crimson', 'line-opacity': .6, 'line-width': 2, 'line-offset': -1 } }, firstSymbolId); map.addLayer({ 'id': 'tract-areas-fill', 'type': 'fill', 'source': { 'type': 'geojson', 'data': data['tract_data'] }, 'paint': { 'fill-color': 'gold', 'fill-opacity': .2, } }, firstSymbolId); mapDiv.classed('active', true); mapCanvas.classed('active', true); map.resize(); map.fitBounds(tractBounds, { padding: 50 }); addOverlay(); addInfoBox(data); }) .catch(() => { d3.select("#loading").classed('in-progress', false); displayError(); }); }; var geocodeAddressMSA = (fips) => { addLoadingImg(); var base_url = root_url + 'geocoder/?msa_fips='; fetch(base_url + fips) .then((resp) => resp.json()) .then((data) => { var layers = map.getStyle().layers; var firstSymbolId = getFirstSymbolLayer(map); var metroBounds = turf.extent(data['metro_data']['geometry']); map.addLayer({ 'id': 'urban-areas-fill', 'type': 'line', 'source': { 'type': 'geojson', 'data': data['metro_data'] }, 'paint': { 'line-color': 'crimson', 'line-opacity': 1, 'line-width': 2, 'line-offset': -1 } }, firstSymbolId); mapDiv.classed('active', true); mapCanvas.classed('active', true); map.resize(); map.fitBounds(metroBounds, { padding: 25 }); addOverlay(); addInfoBoxMSA(data); }) .catch(() => { d3.select("#loading").classed('in-progress', false); displayError(); }); }; var geocodeAddress = (address) => { addLoadingImg(); var base_url = root_url + 'geocoder/?q='; fetch(base_url + address) // Call the fetch function passing the url of the API as a parameter .then((resp) => resp.json()) .then((data) => { var layers = map.getStyle().layers; var firstSymbolId = getFirstSymbolLayer(map); var tractBounds = turf.extent(data['tract_data']['geometry']); map.addLayer({ 'id': 'urban-areas-fill', 'type': 'line', 'source': { 'type': 'geojson', 'data': data['metro_data'] }, 'paint': { 'line-color': 'crimson', 'line-opacity': .6, 'line-width': 2, 'line-offset': -2 } }, firstSymbolId); map.addLayer({ 'id': 'tract-areas-fill', 'type': 'fill', 'source': { 'type': 'geojson', 'data': data['tract_data'] }, 'paint': { 'fill-color': 'gold', 'fill-opacity': .2, } }, firstSymbolId); map.addSource('single-point', { "type": "geojson", "data": { "type": "FeatureCollection", "features": [{ "type": "Feature", "geometry": { "type": "Point", "coordinates": data['address_point'] } }] } }); mapDiv.classed('active', true); mapCanvas.classed('active', true); map.resize(); map.fitBounds(tractBounds, { padding: 50 }); map.addLayer({ "id": "single-point", "source": "single-point", "type": "circle", "paint": { "circle-radius": 5, "circle-color": "gold" } }); addOverlay(); addInfoBox(data); gtag('event', 'Address search', { 'event_category': 'Redlining', 'event_label': data['tract_data']['properties']['geoid'], 'value': 0 }); }) .catch(() => { d3.select("#loading").classed('in-progress', false); displayError(); }); }; var addOverlay = () => { // Adds overlay onto map var overlay = d3.select("#overlay").classed("active", true); d3.select('#legend').classed('hidden', true); d3.select('#bottom-right').classed('hidden', true); overlay.append('button').on('click', () => { overlay.classed('active', false); d3.select("#info-box").html(""); d3.select('#legend').classed('active', false); d3.select("#top-left").classed('active', false); d3.select("#map").classed('active', false); d3.select(".mapboxgl-canvas").classed('active', false); d3.select('#legend').classed('hidden', false); d3.select('#bottom-right').classed('hidden', false); map.resize(); removeUrbanOutline(); removeTractOutline(); removeSinglePoint(); updateURL(null, null); }).classed('close', true) .append('span').classed('glyphicon glyphicon-plus', true); }; var addInfoBoxMSA = (data) => { // Adds overlay information box on metro area click d3.select("#loading").classed('in-progress', false); d3.select("#top-left").classed('active', true); d3.select('#legend').classed('active', true); d3.select("#map").classed('active', true); var info = d3.select("#info-box"); info.html(""); info.append('div').classed('tag', true).text('Metro Area'); info.append('h3').text(formatMetroArea(data['metro_data']['properties']['name'])); info.append('p').html(data['metro_data']['properties']['regression_sentence']); info.append('p').append('em').text('Zoom in more or search for an address above to see details for each census tract.') info.append('hr'); info.append('div').classed('tag', true).text('Banks'); info.append('p').text('Search for banks in this metro area to see their local lending data by race and ethnicity.'); createBankSearch(info, data['metro_data']['properties']['msa_fips']); } var addInfoBox = (data) => { // Adds overlay information box on address search or census tract selection console.log(data['tract_data']['properties']); d3.select("#loading").classed('in-progress', false); d3.select("#top-left").classed('active', true); d3.select('#legend').classed('active', true); var info = d3.select("#info-box"); info.html(""); info.append('div').classed('tag', true).text('Census Tract'); info.append('h3').text(data['tract_data']['properties']['namelsad'] + ", " + data['tract_data']['properties']['loan_data']['county_name'] + ", " + data['tract_data']['properties']['loan_data']['state']); info.append('p').html( "2016 population: " + numberWithCommas(data['tract_data']['properties']['loan_data']['total_population']) + ""); var pop_list = info.append('figure').classed('clear', true).append('ul').classed('demog clear', true); data['tract_data']['properties']['loan_data']['population_segments'].forEach((race) => { if (race['pop_pct'] > 0) { if (race['race_ethnicity'] != "Other Races" ) { pop_list.append('li').classed(race['race_ethnicity'], true).html(toTitleCase(race['race_ethnicity']) + ': ' + round(race['pop_pct']) + ''); } else if (race['race_ethnicity'] == "Other Races") { pop_list.append('li').classed(race['race_ethnicity'], true).html(race['race_ethnicity'].toLowerCase() + ': ' + round(race['pop_pct']) + ''); }; } }); info.append('div').classed('tag', true).text('Loans'); info.append('h3').text('Conventional home loans, 2015 and 2016') if (data['tract_data']['properties']['loan_data']['segments'][0]['apps_count'] > 0) { var figure = info.append('figure') var table = figure.append('table').classed('demog', true).append('tbody'); table.append('tr').html('Race/ethnicityApplicationsLoans madeDenialsOther outcome'); data['tract_data']['properties']['loan_data']['segments'].forEach((race) => { if (race['apps_count'] > 0 ) { var tr = table.append('tr').classed(race['race_ethnicity'], true); tr.append('td').text(toTitleCase(race['race_ethnicity'])); tr.append('td').attr('data-title', "Applications").text(race['apps_count']); if (race['orig_count'] > 0 ) { tr.append('td').attr('data-title', "Loans made").text(race['orig_count'] + ' (' + round(race['origination_rate']) + ')');} else { tr.append('td').attr('data-title', "Loans made").text(race['orig_count']); } if (race['den_count'] > 0 ) { tr.append('td').attr('data-title', "Loans denied").text(race['den_count'] + ' (' + round(race['denial_rate']) + ')');} else { tr.append('td').attr('data-title', "Loans denied").text(race['den_count']); } if (race['other_outcome_count'] > 0 ) { tr.append('td').attr('data-title', "Other outcome").text(race['other_outcome_count'] + ' (' + round(race['other_outcome_rate']) + ')');} else { tr.append('td').attr('data-title', "Other outcome").text(race['other_outcome_count']); } }; }); figure.append('figcaption').text('Source: Home Mortgage Disclosure Act data') } else { info.append('hr'); info.append('p').text('No loan data is available for this Census tract.') info.append('hr'); } if (data['metro_data']) { info.append('div').classed('tag', true).text('Metro Area'); info.append('h3').text(formatMetroArea(data['metro_data']['properties']['name'])); info.append('p').html(data['metro_data']['properties']['regression_sentence']); info.append('hr'); info.append('div').classed('tag', true).text('Banks'); info.append('p').text('Search for banks in this metro area to see their local lending data by race and ethnicity.'); createBankSearch(info, data['metro_data']['properties']['msa_fips']); } }; var createBankSearch = (node, fips) => { // Create the search form with autocomplete var base_url = root_url + 'market/?metro_area__msa_fips=' + fips + '&search='; var options = { url: function(phrase) { return base_url + phrase; }, listLocation: "results", getValue: (e) => { return e.bank.name; }, list: { onChooseEvent: () => { handleSelectedItem(); } }, requestDelay: 500 }; var bankInput = node.append('input').attr('id', 'bank-search').attr('type', 'text').attr('placeholder', 'Wells Fargo, Bank of America...').classed('form-control', true); $('#bank-search').easyAutocomplete(options); var resultsDisplay = node.append('div').classed('ui-widget banks-list', true) .append('div').attr('id', 'log').classed('ui-widget-content', true); }; var handleSelectedItem = () => { // For handling the bank search d3.select('.banks-info').remove(); var data = $('#bank-search').getSelectedItemData(); var info = d3.select("#info-box"); var bankContainer = info.append('div').classed('banks-info', true); bankContainer.append('h3').text(data['bank']['name'] + " in the " + formatMetroArea(data['metro_area']['name']) + " metro area"); bankContainer.append('p').text('Conventional home loans, 2015 and 2016'); var table = bankContainer.append('table'); var tHead = table.append('thead').append('tr'); var tBody = table.append('tbody'); tHead.append('th').text('Race/ethnicity'); tHead.append('th').text('Applications'); tHead.append('th').text('Loans made'); tHead.append('th').text('Denials'); tHead.append('th').text('Other outcome'); data['segments'].forEach((race) => { if (race['apps_count'] > 0) { var row = tBody.append('tr').classed(race['race_ethnicity'], true); row.append('td').text(toTitleCase(race['race_ethnicity'])); row.append('td').attr('data-title', 'Applications').text(numberWithCommas(race['apps_count'])); if (race['orig_count'] == 0) { row.append('td').attr('data-title', 'Loans made').text(numberWithCommas(race['orig_count'])); } else {row.append('td').attr('data-title', 'Loans made').text(numberWithCommas(race['orig_count']) + " (" + round(race['origination_rate']) + ")"); } if (race['den_count'] == 0) { row.append('td').attr('data-title', 'Loans denied').text(numberWithCommas(race['den_count'])); } else { row.append('td').attr('data-title', 'Loans denied').text(numberWithCommas(race['den_count']) + " (" + round(race['denial_rate']) + ")"); } if (race['other_outcome_count'] == 0) { row.append('td').attr('data-title', 'Other outcome').text(numberWithCommas(race['other_outcome_count'])); } else { row.append('td').attr('data-title', 'Other outcome').text(numberWithCommas(race['other_outcome_count']) + " (" + round(race['other_outcome_rate']) + ")"); } } }) bankContainer.append('h3').html(data['bank']['name'] + "'s share of the " + formatMetroArea(data['metro_area']['name']) + " metro area") var msFig = bankContainer.append('figure'); var mstable = msFig.append('table'); var mstHead = mstable.append('thead').append('tr'); var mstBody = mstable.append('tbody'); mstHead.append('th').text('Race/ethnicity'); mstHead.append('th').text('Applications'); mstHead.append('th').text('Loans made'); mstHead.append('th').text('Denials'); data['segments'].forEach((race) => { if (race['apps_marketshare'] > 0) { var row = mstBody.append('tr').classed(race['race_ethnicity'], true); row.append('td').text(toTitleCase(race['race_ethnicity'])); row.append('td').attr('data-title', 'Applications').text(round(race['apps_marketshare'])); row.append('td').attr('data-title', 'Loans made').text(round(race['orig_marketshare'])); row.append('td').attr('data-title', 'Loans denied').text(round(race['den_marketshare'])); } }) msFig.append('figcaption').text('Source: Home Mortgage Disclosure Act data'); gtag('event', 'Bank search', { 'event_category': 'Redlining', 'event_label': data['bank']['bank_id'], 'value': 0 }); }; var createLegend = () => { var swatchContainer = d3.select('.swatch-container'); colorScale.forEach((color) => { swatchContainer.append('div').attr('style', 'background-color: ' + color[1]); }); }; createLegend(); var displayError = () => { var error = d3.select('body').append('div').classed('warning-container', true); error.append('div').classed('warning', true) .html('Error! Something went wrong. Please try again'); setTimeout(() => { error.remove(); }, 4000); }; // Formats metro names for display var formatMetroArea = (str) => { var split = str.split(","); var city = split[0]; var state = split[1]; var a = city.replace(/-/gi, " - "); var b = a.replace(/\w\S*/g, (txt) => { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); return (b + ", " + state); }; // Format in title case const toTitleCase = (str) => { return str.replace(/\w\S*/g, (txt) => {return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}); }; // Round number for percentages const round = (num) => { if (num == 0) { return "0%" } else if (num < .5 ) { return "< 1%" } else { return Math.round(num) + "%"; } }; const numberWithCommas = (num) => { return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); }; var hasTouch = () => { return 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; }