Create a gist now

Instantly share code, notes, and snippets.

@mbostock /.block
Last active Jul 3, 2018

Embed
What would you like to do?
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]]}
@bsn

This comment has been minimized.

Show comment
Hide comment
@bsn

bsn Aug 4, 2011

Hi, you have issue when building svg:circle, so it doesn't work in Firefox and all spots has wrong coordinates(because of padding inside the svg element). I forked your gist and made changes so you can take a look and make changes in your code if you want. http://bl.ocks.org/1125458

bsn commented Aug 4, 2011

Hi, you have issue when building svg:circle, so it doesn't work in Firefox and all spots has wrong coordinates(because of padding inside the svg element). I forked your gist and made changes so you can take a look and make changes in your code if you want. http://bl.ocks.org/1125458

@mbostock

This comment has been minimized.

Show comment
Hide comment
@mbostock

mbostock Aug 4, 2011

Thanks, updated!

Owner

mbostock commented Aug 4, 2011

Thanks, updated!

@tylercloke

This comment has been minimized.

Show comment
Hide comment
@tylercloke

tylercloke Aug 12, 2012

Very cool, just what I was looking for. <3 d3!

Very cool, just what I was looking for. <3 d3!

@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Nov 26, 2012

Thanks. This is awesome!!

ghost commented Nov 26, 2012

Thanks. This is awesome!!

@typeofgraphic

This comment has been minimized.

Show comment
Hide comment
@typeofgraphic

typeofgraphic Jan 4, 2013

Marvellous. I've attempted an adaption which uses geocoding to position the markers

https://gist.github.com/4458566

Console log shows the geo function is pulling six lat/lon figures. The problem is that it is not attaching each lat/long to each marker, even though the transform function (should be) passed the right data.

Is it the case the the geo function is running 6 times, then passing the final amount to a single marker? If so, how can this be changed?

I am developing my JS skills as the code probably shows, much appreciation in advance of any help.

Marvellous. I've attempted an adaption which uses geocoding to position the markers

https://gist.github.com/4458566

Console log shows the geo function is pulling six lat/lon figures. The problem is that it is not attaching each lat/long to each marker, even though the transform function (should be) passed the right data.

Is it the case the the geo function is running 6 times, then passing the final amount to a single marker? If so, how can this be changed?

I am developing my JS skills as the code probably shows, much appreciation in advance of any help.

@patricksurry

This comment has been minimized.

Show comment
Hide comment
@patricksurry

patricksurry Sep 16, 2013

Here's a fork that shows how to use d3.geo machinery to draw geographic objects into the google overlay layer, including correct clipping etc.

https://gist.github.com/patricksurry/6511981

Here's a fork that shows how to use d3.geo machinery to draw geographic objects into the google overlay layer, including correct clipping etc.

https://gist.github.com/patricksurry/6511981

@mickdee

This comment has been minimized.

Show comment
Hide comment
@mickdee

mickdee Oct 26, 2013

How might I size each dot based on a value in the json? If I added another key : value pair to each object, how can this be selected for binding to the circle elements?

Me example data is below. I'm having some difficulty binding this data in lieu of the set used to create the example. What I'm trying to do is use the lat/long for location as the example does, but bind the value for all "in" values to the size of each circle.

Any help is much appreciated, thanks!

{
"station":[
{
"Montebello/19th Ave":[-112.099655,33.520002],
"in": 895666,
"out": 913526
},
{
"19th Ave/Camelback Rd":[-112.09958,33.509661],
"in": 496179,
"out": 483762
},
{
"7th Ave/Camelback Rd":[-112.082757,33.509304],
"in": 285608,
"out": 297610
}
]
}

mickdee commented Oct 26, 2013

How might I size each dot based on a value in the json? If I added another key : value pair to each object, how can this be selected for binding to the circle elements?

Me example data is below. I'm having some difficulty binding this data in lieu of the set used to create the example. What I'm trying to do is use the lat/long for location as the example does, but bind the value for all "in" values to the size of each circle.

Any help is much appreciated, thanks!

{
"station":[
{
"Montebello/19th Ave":[-112.099655,33.520002],
"in": 895666,
"out": 913526
},
{
"19th Ave/Camelback Rd":[-112.09958,33.509661],
"in": 496179,
"out": 483762
},
{
"7th Ave/Camelback Rd":[-112.082757,33.509304],
"in": 285608,
"out": 297610
}
]
}

@nickbenes

This comment has been minimized.

Show comment
Hide comment
@nickbenes

nickbenes Nov 3, 2013

@mickdee: Sounds like you have two questions here so I'll answer them separately.

First, to do data binding for your sample set instead of

      var marker = layer.selectAll("svg")
          .data(d3.entries(data))

you'll want to do

      var marker = layer.selectAll("svg")
          .data(data.stations)

because you have an array of objects where Mike had everything as properties of a top-level object. (NB: this also assumes that your data is in a separate "stations.json" file in your root directory; there are a few more lines to change if you have it somewhere else.) You will also need to change the line

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

but with your current layout I can't think of a way to do it that doesn't seem ugly and computationally wasteful. If your objects were instead something like

{
"name": "Montebello/19th Ave",
"location": [-112.099655,33.520002],
"in": 895666,
"out": 913526
}

then you could do

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

As for making the radius correspond to the data, you should be able to swap out

.attr("r", 4.5)

with something like

.attr("r", function(d) {return d.in / 10000;})

(the 10000 is an arbitrary number to make the circles reasonable; tweak as needed).
I'm doing something similar and running into a problem with circles not showing up until I zoom in or out -- still trying to figure out what's going on there. But hopefully this gets you pointed in the right direction.

@mickdee: Sounds like you have two questions here so I'll answer them separately.

First, to do data binding for your sample set instead of

      var marker = layer.selectAll("svg")
          .data(d3.entries(data))

you'll want to do

      var marker = layer.selectAll("svg")
          .data(data.stations)

because you have an array of objects where Mike had everything as properties of a top-level object. (NB: this also assumes that your data is in a separate "stations.json" file in your root directory; there are a few more lines to change if you have it somewhere else.) You will also need to change the line

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

but with your current layout I can't think of a way to do it that doesn't seem ugly and computationally wasteful. If your objects were instead something like

{
"name": "Montebello/19th Ave",
"location": [-112.099655,33.520002],
"in": 895666,
"out": 913526
}

then you could do

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

As for making the radius correspond to the data, you should be able to swap out

.attr("r", 4.5)

with something like

.attr("r", function(d) {return d.in / 10000;})

(the 10000 is an arbitrary number to make the circles reasonable; tweak as needed).
I'm doing something similar and running into a problem with circles not showing up until I zoom in or out -- still trying to figure out what's going on there. But hopefully this gets you pointed in the right direction.

@nickbenes

This comment has been minimized.

Show comment
Hide comment
@nickbenes

nickbenes Nov 3, 2013

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?

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

This comment has been minimized.

Show comment
Hide comment
@mickdee

mickdee 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.

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

This comment has been minimized.

Show comment
Hide comment
@Tatiana77

Tatiana77 Nov 21, 2013

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

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

This comment has been minimized.

Show comment
Hide comment
@iwemuiser

iwemuiser Apr 8, 2014

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

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

@constantx

This comment has been minimized.

Show comment
Hide comment
@constantx

constantx Sep 18, 2014

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

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

This comment has been minimized.

Show comment
Hide comment
@constantx

constantx Sep 18, 2014

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

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

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

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

This comment has been minimized.

Show comment
Hide comment
@igwtsoftware

igwtsoftware Mar 11, 2015

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

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

@tgsmith61591

This comment has been minimized.

Show comment
Hide comment
@tgsmith61591

tgsmith61591 Mar 25, 2015

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

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

@tgsmith61591

This comment has been minimized.

Show comment
Hide comment
@tgsmith61591

tgsmith61591 Mar 25, 2015

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)

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

This comment has been minimized.

Show comment
Hide comment
@adamspe

adamspe 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.

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

This comment has been minimized.

Show comment
Hide comment
@xvilar

xvilar 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!

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

This comment has been minimized.

Show comment
Hide comment
@powersparks

powersparks Mar 21, 2016

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?

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

This comment has been minimized.

Show comment
Hide comment
@ashikvs

ashikvs 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 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

This comment has been minimized.

Show comment
Hide comment
@ashikvs

ashikvs 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 :)

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

This comment has been minimized.

Show comment
Hide comment
@lipearu

lipearu Nov 2, 2017

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

lipearu commented Nov 2, 2017

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

@AnushaGanta

This comment has been minimized.

Show comment
Hide comment
@AnushaGanta

AnushaGanta Dec 14, 2017

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.

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