Skip to content

Instantly share code, notes, and snippets.

@mtaptich
Last active December 11, 2015 01:05
Show Gist options
  • Save mtaptich/26c2112e8b846124d516 to your computer and use it in GitHub Desktop.
Save mtaptich/26c2112e8b846124d516 to your computer and use it in GitHub Desktop.
d3 + Google Maps

My first pass at showing time series data on Google Maps.

<!DOCTYPE html>
<meta charset="utf-8">
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<link href="//netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
<style>
html, body, #map-canvas {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.stations, .stations svg {
position: absolute;
}
.stations svg {
width: 60px;
height: 20px;
padding-right: 100px;
}
.stations circle {
fill: #fff;
stroke-width: 0.8px;
fill-opacity:0.45;
}
.legend{
position: absolute;
top: 2px;
left: 20px;
z-index: 100;
}
.start{
position: absolute;
top: 17px;
left: 560px;
z-index: 100;
}
#time{
position: absolute;
top: 77px;
left: 20px;
z-index: 90;
}
.box{
position: absolute;
top: 40px;
left: 20px;
z-index: 90;
fill: #fff;
fill-opacity: 0.9;
}
.caption {
font-weight: bold;
}
.key path {
display: none;
}
.key line {
stroke: #000;
shape-rendering: geometricPrecision;
stroke-width: 1px;
}
.ball{
fill: #FDB515;
stroke: #003A70;
stroke-width: 1px;
}
</style>
<body>
<div class="legend"></div>
<div id="time"></div>
<div class="start">
<div id="carouselButtons">
<button id="playButton" type="button" class="btn btn-default btn-xs" onclick="runtime()">
<span class="glyphicon glyphicon-play"></span>
</button>
<button id="pauseButton" type="button" class="btn btn-default btn-xs" onclick="clearInterval(clock)">
<span class="glyphicon glyphicon-pause"></span>
</button>
</div>
</div>
<div id="map-canvas"></div>
<script src="http://www.ghggo.com/stylesheets/data/stationdata.js"></script>
<script type="text/javascript" src="http://d3js.org/queue.v1.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
<script type="text/javascript" defer>
// Create the Google Map…
var map, topodata, hour = 15, minute = 0, overlays = [], overlay, clock
var stylemap = [{"featureType":"administrative","elementType":"all","stylers":[{"visibility":"on"},{"saturation":-100},{"lightness":20}]},{"featureType":"road","elementType":"all","stylers":[{"visibility":"on"},{"saturation":-100},{"lightness":40}]},{"featureType":"water","elementType":"all","stylers":[{"visibility":"on"},{"saturation":-10},{"lightness":30}]},{"featureType":"landscape.man_made","elementType":"all","stylers":[{"visibility":"simplified"},{"saturation":-60},{"lightness":10}]},{"featureType":"landscape.natural","elementType":"all","stylers":[{"visibility":"simplified"},{"saturation":-60},{"lightness":60}]},{"featureType":"poi","elementType":"all","stylers":[{"visibility":"off"},{"saturation":-100},{"lightness":60}]},{"featureType":"transit","elementType":"all","stylers":[{"visibility":"off"},{"saturation":-100},{"lightness":60}]}];
var radius = d3.scale.sqrt()
.domain([200, 800])
.range([2, 10]);
var color = d3.scale.threshold()
.domain([200, 300, 400, 500, 600, 700])
.range(["#d73027","#fc8d59","#fee08b","#d9ef8b","#91cf60","#1a9850", ""].reverse())
var scaledlegend = (function(){
var margin = {top: 40, right: 20, bottom: 30, left: 20},
width = 600 - margin.left - margin.right,
height = 70 - margin.top - margin.bottom;
var formatNumber = d3.format(",d");
var x = d3.scale.linear()
.domain([200,800])
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(13)
.tickValues(color.domain())
var svg = d3.select(".legend").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("rect")
.attr("width", width + margin.left + margin.right)
.attr("height",height + margin.top + margin.bottom-5)
.attr("rx", 6)
.attr("ry", 6)
.attr("class", "box")
.attr("transform", "translate(" + (-margin.left) + "," + (-margin.top+5) + ")");
var g = svg.append("g")
.attr("class", "key")
g.selectAll("rect")
.data(color.range().map(function(d, i) {
return {
x0: i ? x(color.domain()[i - 1]) : x.range()[0],
x1: i < color.domain().length ? x(color.domain()[i]) : x.range()[1],
z: d
};
}))
.enter().append("rect")
.attr("height", 8)
.attr("x", function(d) { return d.x0; })
.attr("width", function(d) { return d.x1 - d.x0; })
.style("fill", function(d) { return d.z; });
g.call(xAxis).append("text")
.attr("class", "caption")
.attr("y", -6)
.text("Grams of Greenhouse Gases per Kilometer Driven")
.style("font-size", 20)
})()
var addoverlay = function(hour, minute){
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().overlayMouseTarget).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(topodata)
.each(transform) // update existing markers
.enter().append("svg:svg")
.each(transform)
.attr("class", "marker")
// Add a circle.
marker.append("svg:circle")
.attr("class", function(d){
if (stationdata[parseInt(d.id)] != undefined && stationdata[parseInt(d.id)][hour] != undefined && stationdata[parseInt(d.id)][hour][minute] != undefined){
return "s"+parseInt(d.id)
}else{
return "err"
}
})
.attr("r", function(d){
if (stationdata[parseInt(d.id)] != undefined && stationdata[parseInt(d.id)][hour] != undefined && stationdata[parseInt(d.id)][hour][minute] != undefined){
return radius(stationdata[parseInt(d.id)][hour][minute])
}else{
return 0
}
})
.attr("cx", padding)
.attr("cy", padding)
.style("fill", function(d){
if (stationdata[parseInt(d.id)] != undefined && stationdata[parseInt(d.id)][hour] != undefined && stationdata[parseInt(d.id)][hour][minute] != undefined){
return color(stationdata[parseInt(d.id)][hour][minute])
}else{
return "#eee"
}
})
.style("stroke", function(d){
if (stationdata[parseInt(d.id)] != undefined && stationdata[parseInt(d.id)][hour] != undefined && stationdata[parseInt(d.id)][hour][minute] != undefined){
return color(stationdata[parseInt(d.id)][hour][minute])
}else{
return "#eee"
}
})
function transform(d) {
d = new google.maps.LatLng(d.geometry.coordinates[1], d.geometry.coordinates[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);
}
function initialize() {
var myLatlng = new google.maps.LatLng(34.07, -118.2500);
var myOptions = {
zoom: 10,
center: myLatlng,
disableDefaultUI: true,
zoomControl: true,
mapTyeControl:true,
zoomControlOptions: {
style: google.maps.ZoomControlStyle.DEFAULT,
position: google.maps.ControlPosition.TOP_RIGHT
},
mapTypeId: google.maps.MapTypeId.ROADMAP,
styles: stylemap
};
map = new google.maps.Map(document.getElementById('map-canvas'), myOptions);
addoverlay(hour, minute)
}
function checkeverything(){
d3.json("stations.json", function(s) {
topodata = topojson.feature(s, s.objects.stations).features;
initialize();
})
}
google.maps.event.addDomListener(window, 'load', checkeverything);
var updateoverlay = function(hour, minute){
var stationids = d3.values(d3.selectAll("circle").data()).map(function(d){ return parseInt(d.id)});
stationids.forEach(function(d){
var current = d3.select(".s"+d)
current.transition()
.duration(200)
.attr("r", function(){
if (stationdata[parseInt(d)] != undefined && stationdata[parseInt(d)][hour] != undefined && stationdata[parseInt(d)][hour][minute] != undefined){
return radius(stationdata[parseInt(d)][hour][minute])
}else{
return 0
}
})
.style("fill", function(){
if (stationdata[parseInt(d)] != undefined && stationdata[parseInt(d)][hour] != undefined && stationdata[parseInt(d)][hour][minute] != undefined){
return color(stationdata[parseInt(d)][hour][minute])
}else{
return "#eee"
}
})
.style("stroke", function(){
if (stationdata[parseInt(d)] != undefined && stationdata[parseInt(d)][hour] != undefined && stationdata[parseInt(d)][hour][minute] != undefined){
return color(stationdata[parseInt(d)][hour][minute])
}else{
return "#eee"
}
})
})
}
var updatetime = function(){
var margin = {top: 10, right: 20, bottom: 25, left: 20},
width = 600 - margin.left - margin.right,
height = 40 - margin.top - margin.bottom;
var parseTime = d3.time.format("%H:%M").parse;
var formatTime = d3.time.format("%H:%M")
var x = d3.time.scale()
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(formatTime)
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
var svg = d3.select("#time").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain([parseTime(15+":"+0), parseTime(19+":"+0)]);
svg.append("rect")
.attr("width", width + margin.left + margin.right)
.attr("height",height + margin.top + margin.bottom)
.attr("rx", 6)
.attr("ry", 6)
.attr("class", "box")
.attr("transform", "translate(" + (-margin.left) + "," + (-margin.top) + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var ball = svg.append("g")
ball.append("rect")
.attr("class", "ball")
.attr("width", 4)
.attr("height",10)
.attr("rx", 1)
.attr("ry", 1)
.attr("transform", "translate("+(x(parseTime(15+":"+0)))+",0)")
ballposition = function(hour, minute){
ball.transition().duration(1000).attr("transform", "translate("+(x(parseTime(hour+":"+minute)))+",0)")
}
return ballposition
}()
var runtime = function(){
clock = setInterval(function(){
minute = parseInt(minute+5)
if (minute == 60){ minute = 0; hour = hour +1}
if(hour > 18){ hour = 15; minute= 0;}
updateoverlay(hour, minute)
updatetime(hour, minute)
}, 1000);
}
</script>
</body>
</html>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment