Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active November 14, 2023 03:46
Show Gist options
  • Save mbostock/899711 to your computer and use it in GitHub Desktop.
Save mbostock/899711 to your computer and use it in GitHub Desktop.
Google Maps + D3
license: gpl-3.0
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
<style>
html, body, #map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.stations, .stations svg {
position: absolute;
}
.stations svg {
width: 60px;
height: 20px;
padding-right: 100px;
font: 10px sans-serif;
}
.stations circle {
fill: brown;
stroke: black;
stroke-width: 1.5px;
}
</style>
<div id="map"></div>
<script src="//maps.google.com/maps/api/js?sensor=true"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
// Create the Google Map…
var map = new google.maps.Map(d3.select("#map").node(), {
zoom: 8,
center: new google.maps.LatLng(37.76487, -122.41948),
mapTypeId: google.maps.MapTypeId.TERRAIN
});
// Load the station data. When the data comes back, create an overlay.
d3.json("stations.json", function(error, data) {
if (error) throw error;
var overlay = new google.maps.OverlayView();
// Add the container when the overlay is added to the map.
overlay.onAdd = function() {
var layer = d3.select(this.getPanes().overlayLayer).append("div")
.attr("class", "stations");
// Draw each marker as a separate SVG element.
// We could use a single SVG, but what size would it have?
overlay.draw = function() {
var projection = this.getProjection(),
padding = 10;
var marker = layer.selectAll("svg")
.data(d3.entries(data))
.each(transform) // update existing markers
.enter().append("svg")
.each(transform)
.attr("class", "marker");
// Add a circle.
marker.append("circle")
.attr("r", 4.5)
.attr("cx", padding)
.attr("cy", padding);
// Add a label.
marker.append("text")
.attr("x", padding + 7)
.attr("y", padding)
.attr("dy", ".31em")
.text(function(d) { return d.key; });
function transform(d) {
d = new google.maps.LatLng(d.value[1], d.value[0]);
d = projection.fromLatLngToDivPixel(d);
return d3.select(this)
.style("left", (d.x - padding) + "px")
.style("top", (d.y - padding) + "px");
}
};
};
// Bind our overlay to the map…
overlay.setMap(map);
});
</script>
{"KMAE":[-120.12,36.98,"MADERA MUNICIPAL AIRPORT",[26,1,2,5,6,3,2,1,2,7,29,12,3]],"KSJC":[-121.92,37.37,"SAN JOSE INTERNATIONAL AIRPORT",[28,1,1,1,6,10,5,3,2,4,14,21,7]],"KMCE":[-120.50,37.28,"MERCED MUNICIPAL AIRPORT",[29,1,1,3,7,5,2,1,3,6,12,26,5]],"KMER":[-120.57,37.37,"Merced / Castle Air Force Base",[34,1,1,1,4,5,2,1,1,4,17,22,7]],"KAPC":[-122.28,38.20,"NAPA COUNTY AIRPORT",[23,2,1,6,3,3,8,18,11,13,4,3,5]],"KSUU":[-121.95,38.27,"Fairfield / Travis Air Force Base",[13,7,4,3,3,6,4,13,33,4,1,2,7]],"KSQL":[-122.25,37.52,"San Carlos Airport",[18,3,2,2,3,4,3,2,5,17,16,12,12]],"KSNS":[-121.60,36.67,"SALINAS MUNICIPAL AIRPORT",[21,1,1,6,12,3,1,2,9,21,17,5,1]],"KMOD":[-120.95,37.62,"MODESTO CITY CO SHAM FLD",[27,1,1,2,10,5,1,1,1,3,17,24,8]],"KOAK":[-122.23,37.72,"METRO OAKLAND INTERNATIONAL AIRPORT ",[16,3,3,2,4,6,3,4,9,23,20,6,2]],"KSCK":[-121.23,37.90,"STOCKTON METROPOLITAN AIRPORT ",[21,2,2,3,6,8,2,1,4,15,19,12,4]],"KCCR":[-122.05,38.00,"CONCORD BUCHANAN FIELD",[24,3,2,1,1,5,17,12,9,9,7,6,4]],"KMRY":[-121.85,36.58,"MONTEREY PENINSULA AIRPORT",[26,1,2,9,5,3,4,9,13,14,9,4,1]],"KPAO":[-122.12,37.47,"Palo Alto Airport",[31,3,1,1,2,5,1,1,1,4,10,25,14]],"KSAC":[-121.50,38.50,"SACRAMENTO EXECUTIVE AIRPORT ",[32,1,0,1,3,11,12,16,5,2,4,9,3]],"KHWD":[-122.12,37.67,"HAYWARD AIR TERMINAL",[20,2,7,2,2,6,3,3,6,23,18,6,2]],"KSTS":[-122.82,38.50,"SANTA ROSA SONOMA COUNTY",[46,1,0,1,5,13,10,4,3,3,4,6,3]],"KSMF":[-121.60,38.70,"SACRAMENTO INTERNATIONAL AIRPORT",[19,2,1,2,4,21,18,8,3,2,5,12,4]],"KNUQ":[-122.05,37.43,"MOFFETT FIELD",[35,3,1,1,4,7,2,1,2,5,6,17,15]],"KRHV":[-121.82,37.33,"San Jose / Reid / Hillv",[35,0,0,1,4,4,2,1,1,10,28,11,1]],"KWVI":[-121.78,36.93,"WATSONVILLE MUNICIPAL AIRPORT ",[44,1,2,3,4,5,7,9,8,4,6,5,2]],"KMHR":[-121.30,38.55,"Sacramento, Sacramento Mather Airport",[21,1,1,2,8,15,12,12,7,4,5,7,3]],"KVCB":[-121.95,38.38,"VACAVILLE NUT TREE AIRPORT",[36,2,1,1,2,6,10,18,10,2,2,5,6]],"KSFO":[-122.37,37.62,"SAN FRANCISCO INTERNATIONAL AIRPORT ",[13,3,3,2,3,4,4,4,7,31,20,2,3]],"KLVK":[-121.82,37.70,"LIVERMORE MUNICIPAL AIRPORT ",[32,2,7,3,1,1,2,7,9,17,16,2,1]]}
@nickbenes
Copy link

This is a great example, but I really want to make it interactive and I can't figure out how to register click events with this. I'm not sure if the google map is eating them or something, but I'm stuck. My d3 approach was

marker.on('click', function(d,i){
    console.log('clicked');
});

inside overlay.onAdd, but I'm getting nothing in the console when I click. Similar results setting up listeners with google.maps.events or jQuery.

Any suggestions?

@mickdee
Copy link

mickdee commented Nov 19, 2013

Thanks for the great help nickbenes! I've made some progress with dots sized and colored according to data values for each station.

I've also got click events working to swap in new values for each station. The output to a user should look fine, but behind the scenes I'm doing this pretty sloppily at the moment.

Check out the example here: http://mikemcdearmon.com/portfolio/light-rail-boardings

And the code here: https://github.com/mickdee/AZD3/blob/master/maps/AZ-LRT.html

My next task is getting tooltips to work. I've tried just about every way I can think of but can't successfully get mouseover events to work properly on the svg circles either with d3 or plain ol' css.

@Tatiana77
Copy link

Hello,

I am new here so please accept my apologizes in advance if I am asking something wrongly. If possible I would like to ask you for help, I am using the code above in this article to plot two set of circles in a google map with some variations, so I am getting the two sets from two different files, for that I am using 2 overlays, that are added to the map separately, but it is plotting only the first set of circles, if possible could I post my code and the two files anywhere to get any help with that?

Thank you in advance.
Tatiana

@iwemuiser
Copy link

Has a solution been found for this mouse event problem? I also would like to create some more interaction with my graphs.

@constantx
Copy link

I've implemented this and the marker shows up. However, when zooming in and out, the marker doesn't update its position (though panning works just fine). Any hint?

cc @mbostock

@constantx
Copy link

ah, nm, it turns out some css style is needed. In my case, this was needed:

.stations, .stations svg {
  position: absolute;
}

@igwtsoftware
Copy link

any examples on how to add d3 circles in real time as data is received?

@tgsmith61591
Copy link

I, too, am looking for a solution to mouseover/onclick events for the svg circles. Anyone found anything?

@tgsmith61591
Copy link

I ended up answering my own question. You have to make a couple changes. First make the overlayLayer an overlayMouseTarget like so:

var layer = d3.select(this.getPanes().overlayMouseTarget).append("div") .attr("class", "stations");

I added the following attr to the circle:

marker.append("svg:circle").attr("r", 4.5).attr("cx", padding).attr("cy", padding).on("click",toggleExpand);

...and then had to define the toggleExpand() method:

function toggleExpand() { if(d3.select(this).attr("r") == 4.5) { d3.select(this).transition().duration(100).attr("r",7) } else { d3.select(this).transition().duration(100).attr("r",4.5) } }

(please forgive the formatting, I can't make the function look good)

@adamspe
Copy link

adamspe commented May 29, 2015

Thanks for this example it got me heading in the right direction.

I copied it (didn't fork) and made some changes for my own purposes so that I could use a single SVG instead of one per map marker and like several others mentioned so I could have my markers get mouse events (using overlayMouseTarget instead of overlayLayer.

I did a small writeup of my changes here and the end result is here.

@xvilar
Copy link

xvilar commented Sep 4, 2015

I've already loaded data from Elasticsearch, doing a query to ES, into an object called "resp"... I've this values:

"hits": {
"hits": [
"_source": {"location": "43.54526,-5.661926"},
"_source": {"location": "40.41669,-3.700346"}
]}

so values from "resp.hits.hits._source.location" is what i want to draw in the map

I've tried to adapt the code with

var marker = layer.selectAll("svg")
.data(resp.hits.hits)

and:

d = new google.maps.LatLng(d.location)
or
d = new google.maps.LatLng(d.location[1],d.location[0])

It doens't works ¿? Thanks!

@powersparks
Copy link

In some cases, the map is part of a document, that contains some css like the following:
*, *::before, *::after {
box-sizing: border-box;
}

if you add the above css to the example, the overlay div is still placed but not rendered.
Highlighting each html svg element shows it's in the right location, but the content is not displayed.

any idea why?

@ashikvs
Copy link

ashikvs commented Oct 25, 2017

hi mbostock, i took a copy of your code and tweaked it a little bit to get live lat and long from an api, by this i could get live tracking of my car!
but now i want just only the live locations to be plotted on the map , and aviod the trailing points, but i dont know how to remove the trailing points.. can someone tell me how?
tracking

@ashikvs
Copy link

ashikvs commented Oct 26, 2017

i got the solution to the described problem, we have to remove the existing svg layer us9ing the folowing command
d3.select("svg").remove();
Thanks anyways :)

@lipearu
Copy link

lipearu commented Nov 2, 2017

if i add the <div id="map"></div> inside another div, the map not show

@AnushaGanta
Copy link

Hi,
Can someone help me with drawing a line between two points? My main idea is to not just plot nodes but to plot a graph over a map. Since, the above code shows how to plot the nodes, can some one help me with plotting lines between the plotted nodes?
Thank you in advance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment