Combine and Visualize Multiple Data Sources Stay organized with collections Save and categorize content based on your preferences.
Page Summary
This tutorial demonstrates how to build an interactive choropleth map of the United States using the Google Maps JavaScript API and US Census data.
Users can select data categories from a dropdown menu, view data represented by color gradients within state boundaries, and see detailed information on hover.
The code utilizes GeoJSON for state boundaries, the US Census API for demographic data, and JavaScript for map functionality and styling.
Key concepts covered include choropleth mapping, data styling, and adding interactivity to web maps using the Google Maps API.
You'll need a Google Maps API key to run the code and explore the interactive map visualization.
Overview
This tutorial shows you how to display data from multiple sources on a Google map. As an example, the choropleth map below uses two different sources to highlight various US states, and display state-specific data.
The map uses data from a GeoJSON file to display polygons that define US state boundaries. It can also present data on the map corresponding to each state, which comes from a simulated query to the US Census API.
Select a category of data from the control dropdown menu to update the polygons on the map. You can also hover over a state polygon to view state-specific information in a data box control on the map.
The sample below shows the entire code you need to create this map.
TypeScript
constmapStyle:google.maps.MapTypeStyle[]=[{stylers:[{visibility:"off"}],},{featureType:"landscape",elementType:"geometry",stylers:[{visibility:"on"},{color:"#fcfcfc"}],},{featureType:"water",elementType:"geometry",stylers:[{visibility:"on"},{color:"#bfd4ff"}],},];letmap:google.maps.Map;letcensusMin=Number.MAX_VALUE,censusMax=-Number.MAX_VALUE;functioninitMap():void{// load the mapmap=newgoogle.maps.Map(document.getElementById("map")asHTMLElement,{center:{lat:40,lng:-100},zoom:4,styles:mapStyle,});// set up the style rules and events for google.maps.Datamap.data.setStyle(styleFeature);map.data.addListener("mouseover",mouseInToRegion);map.data.addListener("mouseout",mouseOutOfRegion);// wire up the buttonconstselectBox=document.getElementById("census-variable")asHTMLSelectElement;google.maps.event.addDomListener(selectBox,"change",()=>{clearCensusData();loadCensusData(selectBox.options[selectBox.selectedIndex].value);});// state polygons only need to be loaded once, do them nowloadMapShapes();}/** Loads the state boundary polygons from a GeoJSON source. */functionloadMapShapes(){// load US state outline polygons from a GeoJson filemap.data.loadGeoJson("https://storage.googleapis.com/mapsdevsite/json/states.js",{idPropertyName:"STATE"});// wait for the request to complete by listening for the first feature to be// addedgoogle.maps.event.addListenerOnce(map.data,"addfeature",()=>{google.maps.event.trigger(document.getElementById("census-variable")asHTMLElement,"change");});}/** * Loads the census data from a simulated API call to the US Census API. * * @param {string} variable */functionloadCensusData(variable:string){// load the requested variable from the census API (using local copies)constxhr=newXMLHttpRequest();xhr.open("GET",variable+".json");xhr.onload=function(){constcensusData=JSON.parse(xhr.responseText)asany;censusData.shift();// the first row contains column namescensusData.forEach((row:string)=>{constcensusVariable=parseFloat(row[0]);conststateId=row[1];// keep track of min and max valuesif(censusVariable <censusMin){censusMin=censusVariable;}if(censusVariable >censusMax){censusMax=censusVariable;}conststate=map.data.getFeatureById(stateId);// update the existing row with the new dataif(state){state.setProperty("census_variable",censusVariable);}});// update and display the legend(document.getElementById("census-min")asHTMLElement).textContent=censusMin.toLocaleString();(document.getElementById("census-max")asHTMLElement).textContent=censusMax.toLocaleString();};xhr.send();}/** Removes census data from each shape on the map and resets the UI. */functionclearCensusData(){censusMin=Number.MAX_VALUE;censusMax=-Number.MAX_VALUE;map.data.forEach((row)=>{row.setProperty("census_variable",undefined);});(document.getElementById("data-box")asHTMLElement).style.display="none";(document.getElementById("data-caret")asHTMLElement).style.display="none";}/** * Applies a gradient style based on the 'census_variable' column. * This is the callback passed to data.setStyle() and is called for each row in * the data set. Check out the docs for Data.StylingFunction. * * @param {google.maps.Data.Feature} feature */functionstyleFeature(feature:google.maps.Data.Feature){constlow=[5,69,54];// color of smallest datumconsthigh=[151,83,34];// color of largest datumletcensusVariable=feature.getProperty("census_variable")asnumber;// delta represents where the value sits between the min and maxconstdelta=(censusVariable-censusMin)/(censusMax-censusMin);constcolor:number[]=[];for(leti=0;i <3;i++){// calculate an integer color based on the deltacolor.push((high[i]-low[i])*delta+low[i]);}// determine whether to show this shape or notletshowRow=true;if(censusVariable==null||isNaN(censusVariable)){showRow=false;}letoutlineWeight=0.5,zIndex=1;if(feature.getProperty("state")==="hover"){outlineWeight=zIndex=2;}return{strokeWeight:outlineWeight,strokeColor:"#fff",zIndex:zIndex,fillColor:"hsl("+color[0]+","+color[1]+"%,"+color[2]+"%)",fillOpacity:0.75,visible:showRow,};}/** * Responds to the mouse-in event on a map shape (state). * * @param {?google.maps.MapMouseEvent} e */functionmouseInToRegion(e:any){// set the hover state so the setStyle function can change the bordere.feature.setProperty("state","hover");constpercent=((e.feature.getProperty("census_variable")-censusMin)/(censusMax-censusMin))*100;// update the label(document.getElementById("data-label")asHTMLElement).textContent=e.feature.getProperty("NAME");(document.getElementById("data-value")asHTMLElement).textContent=e.feature.getProperty("census_variable").toLocaleString();(document.getElementById("data-box")asHTMLElement).style.display="block";(document.getElementById("data-caret")asHTMLElement).style.display="block";(document.getElementById("data-caret")asHTMLElement).style.paddingLeft=percent+"%";}/** * Responds to the mouse-out event on a map shape (state). * */functionmouseOutOfRegion(e:any){// reset the hover state, returning the border to normale.feature.setProperty("state","normal");}declareglobal{interfaceWindow{initMap:()=>void;}}window.initMap=initMap;
JavaScript
constmapStyle=[{stylers:[{visibility:"off"}],},{featureType:"landscape",elementType:"geometry",stylers:[{visibility:"on"},{color:"#fcfcfc"}],},{featureType:"water",elementType:"geometry",stylers:[{visibility:"on"},{color:"#bfd4ff"}],},];letmap;letcensusMin=Number.MAX_VALUE,censusMax=-Number.MAX_VALUE;functioninitMap(){// load the mapmap=newgoogle.maps.Map(document.getElementById("map"),{center:{lat:40,lng:-100},zoom:4,styles:mapStyle,});// set up the style rules and events for google.maps.Datamap.data.setStyle(styleFeature);map.data.addListener("mouseover",mouseInToRegion);map.data.addListener("mouseout",mouseOutOfRegion);// wire up the buttonconstselectBox=document.getElementById("census-variable");google.maps.event.addDomListener(selectBox,"change",()=>{clearCensusData();loadCensusData(selectBox.options[selectBox.selectedIndex].value);});// state polygons only need to be loaded once, do them nowloadMapShapes();}/** Loads the state boundary polygons from a GeoJSON source. */functionloadMapShapes(){// load US state outline polygons from a GeoJson filemap.data.loadGeoJson("https://storage.googleapis.com/mapsdevsite/json/states.js",{idPropertyName:"STATE"},);// wait for the request to complete by listening for the first feature to be// addedgoogle.maps.event.addListenerOnce(map.data,"addfeature",()=>{google.maps.event.trigger(document.getElementById("census-variable"),"change",);});}/** * Loads the census data from a simulated API call to the US Census API. * * @param {string} variable */functionloadCensusData(variable){// load the requested variable from the census API (using local copies)constxhr=newXMLHttpRequest();xhr.open("GET",variable+".json");xhr.onload=function(){constcensusData=JSON.parse(xhr.responseText);censusData.shift();// the first row contains column namescensusData.forEach((row)=>{constcensusVariable=parseFloat(row[0]);conststateId=row[1];// keep track of min and max valuesif(censusVariable <censusMin){censusMin=censusVariable;}if(censusVariable >censusMax){censusMax=censusVariable;}conststate=map.data.getFeatureById(stateId);// update the existing row with the new dataif(state){state.setProperty("census_variable",censusVariable);}});// update and display the legenddocument.getElementById("census-min").textContent=censusMin.toLocaleString();document.getElementById("census-max").textContent=censusMax.toLocaleString();};xhr.send();}/** Removes census data from each shape on the map and resets the UI. */functionclearCensusData(){censusMin=Number.MAX_VALUE;censusMax=-Number.MAX_VALUE;map.data.forEach((row)=>{row.setProperty("census_variable",undefined);});document.getElementById("data-box").style.display="none";document.getElementById("data-caret").style.display="none";}/** * Applies a gradient style based on the 'census_variable' column. * This is the callback passed to data.setStyle() and is called for each row in * the data set. Check out the docs for Data.StylingFunction. * * @param {google.maps.Data.Feature} feature */functionstyleFeature(feature){constlow=[5,69,54];// color of smallest datumconsthigh=[151,83,34];// color of largest datumletcensusVariable=feature.getProperty("census_variable");// delta represents where the value sits between the min and maxconstdelta=(censusVariable-censusMin)/(censusMax-censusMin);constcolor=[];for(leti=0;i <3;i++){// calculate an integer color based on the deltacolor.push((high[i]-low[i])*delta+low[i]);}// determine whether to show this shape or notletshowRow=true;if(censusVariable==null||isNaN(censusVariable)){showRow=false;}letoutlineWeight=0.5,zIndex=1;if(feature.getProperty("state")==="hover"){outlineWeight=zIndex=2;}return{strokeWeight:outlineWeight,strokeColor:"#fff",zIndex:zIndex,fillColor:"hsl("+color[0]+","+color[1]+"%,"+color[2]+"%)",fillOpacity:0.75,visible:showRow,};}/** * Responds to the mouse-in event on a map shape (state). * * @param {?google.maps.MapMouseEvent} e */functionmouseInToRegion(e){// set the hover state so the setStyle function can change the bordere.feature.setProperty("state","hover");constpercent=((e.feature.getProperty("census_variable")-censusMin)/(censusMax-censusMin))*100;// update the labeldocument.getElementById("data-label").textContent=e.feature.getProperty("NAME");document.getElementById("data-value").textContent=e.feature.getProperty("census_variable").toLocaleString();document.getElementById("data-box").style.display="block";document.getElementById("data-caret").style.display="block";document.getElementById("data-caret").style.paddingLeft=percent+"%";}/** * Responds to the mouse-out event on a map shape (state). * */functionmouseOutOfRegion(e){// reset the hover state, returning the border to normale.feature.setProperty("state","normal");}window.initMap=initMap;
CSS
html,body,#map{height:100%;margin:0;padding:0;overflow:hidden;}.nicebox{position:absolute;text-align:center;font-family:"Roboto","Arial",sans-serif;font-size:13px;z-index:5;box-shadow:04px6px-4px#333;padding:5px10px;background:rgb(255,255,255);background:linear-gradient(tobottom,rgb(255,255,255)0%,rgb(245,245,245)100%);border:rgb(229,229,229)1pxsolid;}#controls{top:10px;left:110px;width:360px;height:45px;}#data-box{top:10px;left:500px;height:45px;line-height:45px;display:none;}#census-variable{width:360px;height:20px;}#legend{display:flex;display:-webkit-box;padding-top:7px;}.color-key{background:linear-gradient(toright,hsl(5,69%,54%)0%,hsl(29,71%,51%)17%,hsl(54,74%,47%)33%,hsl(78,76%,44%)50%,hsl(102,78%,41%)67%,hsl(127,81%,37%)83%,hsl(151,83%,34%)100%);flex:1;-webkit-box-flex:1;margin:05px;text-align:left;font-size:1em;line-height:1em;}#data-value{font-size:2em;font-weight:bold;}#data-label{font-size:2em;font-weight:normal;padding-right:10px;}#data-label:after{content:":";}#data-caret{margin-left:-5px;display:none;font-size:14px;width:14px;}
HTML
<html> <head> <title>Mashups with google.maps.Data</title> <link rel="stylesheet" type="text/css" href="./style.css" /> <script type="module" src="./index.js"></script> </head> <body> <div> <div> <select> <option value="https://storage.googleapis.com/mapsdevsite/json/DP02_0066PE" > Percent of population over 25 that completed high school </option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP05_0017E" > Median age </option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP05_0001E" > Total population </option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP02_0016E" > Average family size </option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP03_0088E" > Per-capita income </option> </select> </div> <div> <div>min</div> <div><span>◆</span></div> <div>max</div> </div> </div> <div> <label for="data-value"></label> <span></span> </div> <div></div> <!-- The `defer` attribute causes the script to execute after the full HTML document has been parsed. For non-blocking uses, avoiding race conditions, and consistent behavior across browsers, consider loading using Promises. See https://developers.google.com/maps/documentation/javascript/load-maps-js-api for more information. --> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initMap&v=weekly" defer ></script> </body></html>
Try Sample
Getting started
You can develop your own version of this choropleth map by using the code in this tutorial. To begin doing this, create a new file in a text editor and save it asindex.html.
Read the sections that follow to understand the code that you can add to this file.
Creating a basic map
This section explains the code that sets up a basic map. This may be similar to how you've created maps when getting started with the Maps JavaScript API.
Copy the code below into yourindex.html file. This code loads the Maps JavaScript API, and makes the map fullscreen.
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <title>Mashups with google.maps.Data</title> <style> #map { height: 100%; } /* Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div></div> <script> function initMap() { // load the map map = new google.maps.Map(document.getElementById('map'), { center: {lat: 40, lng: -100}, zoom: 4, styles: mapStyle }); var mapStyle = [{ 'featureType': 'all', 'elementType': 'all', 'stylers': [{'visibility': 'off'}] }, { 'featureType': 'landscape', 'elementType': 'geometry', 'stylers': [{'visibility': 'on'}, {'color': '#fcfcfc'}] }, { 'featureType': 'water', 'elementType': 'labels', 'stylers': [{'visibility': 'off'}] }, { 'featureType': 'water', 'elementType': 'geometry', 'stylers': [{'visibility': 'on'}, {'hue': '#5f94ff'}, {'lightness': 60}] }]; } </script> <script defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"> </script> </body></html> The code within the first script tag is the starting point that runs the program by creating a function calledinitMap that initializes the map object.
The stylers in the code above turn off the visibility of allfeatureTypes on the map like roads, points of interest, landscape, administrative areas, and all theirelementTypes. For a list of all available values forfeatureType andelementType, see theJSON style reference.
ClickYOUR_API_KEY in the code sample, or follow the instructions toget an API key. ReplaceYOUR_API_KEY with your application's API key. After the API is completely loaded, the callback parameter in the script tag below executes theinitMap() function in the HTML file.
<script>defersrc="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"</script>
Creating and styling the map control
The code below creates the following controls on the map:
- A control with a dropdown menu that has 5 different data options.
- A map legend.
- A data box displaying state-specific data which appears when you hover over a polygon.
<divid="controls"class="nicebox"><div><selectid="census-variable"><optionvalue="https://storage.googleapis.com/mapsdevsite/json/DP02_0066PE">Percentofpopulationover25thatcompletedhighschool</option><optionvalue="https://storage.googleapis.com/mapsdevsite/json/DP05_0017E">Medianage</option><optionvalue="https://storage.googleapis.com/mapsdevsite/json/DP05_0001E">Totalpopulation</option><optionvalue="https://storage.googleapis.com/mapsdevsite/json/DP02_0016E">Averagefamilysize</option><optionvalue="https://storage.googleapis.com/mapsdevsite/json/DP03_0088E">Per-capitaincome</option></select></div><divid="legend"><divid="census-min">min</div><divclass="color-key"><spanid="data-caret">◆</span></div><divid="census-max">max</div></div></div><divid="data-box"class="nicebox"><labelid="data-label"for="data-value"></label><spanid="data-value"></span></div>
Use the code below within thestyle tags to style the map controls.
<style>html,body,#map{height:100%;margin:0;padding:0;overflow:hidden;}.nicebox{position:absolute;text-align:center;font-family:"Roboto","Arial",sans-serif;font-size:13px;z-index:5;box-shadow:04px6px-4px#333;padding:5px10px;background:rgb(255,255,255);background:linear-gradient(tobottom,rgba(255,255,255,1)0%,rgba(245,245,245,1)100%);border:rgb(229,229,229)1pxsolid;}#controls{top:10px;left:110px;width:360px;height:45px;}#data-box{top:10px;left:500px;height:45px;line-height:45px;display:none;}#census-variable{width:360px;height:20px;}#legend{display:flex;display:-webkit-box;padding-top:7px}.color-key{background:linear-gradient(toright,hsl(5,69%,54%)0%,hsl(29,71%,51%)17%,hsl(54,74%,47%)33%,hsl(78,76%,44%)50%,hsl(102,78%,41%)67%,hsl(127,81%,37%)83%,hsl(151,83%,34%)100%);flex:1;-webkit-box-flex:1;margin:05px;text-align:left;font-size:1.0em;line-height:1.0em;}#data-value{font-size:2.0em;font-weight:bold}#data-label{font-size:2.0em;font-weight:normal;padding-right:10px;}#data-label:after{content:':'}#data-caret{margin-left:-5px;display:none;font-size:14px;width:14px}</style>
Importing data from the US Census API
The code below queries the US Census Bureau for the most recent census data of all US states, which it receives in JSON format.
functionloadCensusData(variable){// load the requested variable from the census APIvarxhr=newXMLHttpRequest();xhr.open('GET','http://api.census.gov/data/2012/acs5/profile?get='+variable+'&for=state:*&key=YOUR_API_KEY');xhr.onload=function(){varcensusData=JSON.parse(xhr.responseText);censusData.shift();// the first row contains column namescensusData.forEach(function(row){varcensusVariable=parseFloat(row[0]);varstateId=row[1];// keep track of min and max valuesif(censusVariable <censusMin){censusMin=censusVariable;}if(censusVariable >censusMax){censusMax=censusVariable;}// update the existing row with the new datamap.data.getFeatureById(stateId).setProperty('census_variable',censusVariable);});// update and display the legenddocument.getElementById('census-min').textContent=censusMin.toLocaleString();document.getElementById('census-max').textContent=censusMax.toLocaleString();};xhr.send();}
Styling the data
The code below creates the choropleth map by applying a gradient to each polygon in the dataset, based on the census data value. You can style data using aData.StyleOptions object, or a function that returns aData.StyleOptions object.
// set up the style rules and events for google.maps.Datamap.data.setStyle(styleFeature);functionstyleFeature(feature){varlow=[5,69,54];// color of smallest datumvarhigh=[151,83,34];// color of largest datum// delta represents where the value sits between the min and maxvardelta=(feature.getProperty('census_variable')-censusMin)/(censusMax-censusMin);varcolor=[];for(vari=0;i <3;i++){// calculate an integer color based on the deltacolor[i]=(high[i]-low[i])*delta+low[i];}// determine whether to show this shape or notvarshowRow=true;if(feature.getProperty('census_variable')==null||isNaN(feature.getProperty('census_variable'))){showRow=false;}varoutlineWeight=0.5,zIndex=1;if(feature.getProperty('state')==='hover'){outlineWeight=zIndex=2;}return{strokeWeight:outlineWeight,strokeColor:'#fff',zIndex:zIndex,fillColor:'hsl('+color[0]+','+color[1]+'%,'+color[2]+'%)',fillOpacity:0.75,visible:showRow};}
In addition to coloring the polygons, the code below creates an interactive element by adding events that respond to mouse activity. Hovering over a polygon highlights the state border, and simultaneously updates the data box control on the map.
// set up the style rules and events for google.maps.Datamap.data.addListener('mouseover',mouseInToRegion);map.data.addListener('mouseout',mouseOutOfRegion);/** * Responds to the mouse-in event on a map shape (state). * * @param {?google.maps.MapMouseEvent} e */functionmouseInToRegion(e){// set the hover state so the setStyle function can change the bordere.feature.setProperty('state','hover');varpercent=(e.feature.getProperty('census_variable')-censusMin)/(censusMax-censusMin)*100;// update the labeldocument.getElementById('data-label').textContent=e.feature.getProperty('NAME');document.getElementById('data-value').textContent=e.feature.getProperty('census_variable').toLocaleString();document.getElementById('data-box').style.display='block';document.getElementById('data-caret').style.display='block';document.getElementById('data-caret').style.paddingLeft=percent+'%';}/** * Responds to the mouse-out event on a map shape (state). * * @param {?google.maps.MapMouseEvent} e */functionmouseOutOfRegion(e){// reset the hover state, returning the border to normale.feature.setProperty('state','normal');}
Loading the state-boundary polygons
Add the code below after the entireinitMap function. TheloadMapShapes function loads polygons for US state boundaries from a GeoJSON file, using theloadGeoJson method.
/** Loads the state boundary polygons from a GeoJSON source. */functionloadMapShapes(){// load US state outline polygons from a GeoJSON filemap.data.loadGeoJson('https://storage.googleapis.com/mapsdevsite/json/states.js',{idPropertyName:'STATE'});
Add the line below to the end of theinitMap function.
// state polygons only need to be loaded once, do them nowloadMapShapes();
On selecting a data source option from the map control dropdown menu, the map queries theUS Census Data API for the specified variable. To connect the census data with the shape data, the code sets theidPropertyName to 'STATE', which is a common key in both the Census data and in the GeoJson properties.
More information
This demo uses theCensus Bureau Data API, but is not endorsed or certified by the Census Bureau.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2025-12-18 UTC.