I am plotting points on a Google Map (so far so good) and then plotting a line between each point based on the point order in the underlying data (not so good). Like a trail or route.
Unlike the handful of examples I've seen doing this, my data are not in GeoJSON format, and I would really like to keep it that way if at all possible. I have tried to adapt the exampels posted here and here but without success.
My results end up with no lines being drawn, and I can't tell if that's because of a projection error or something else syntactical with D3. I have tried to debug through console.log() statements, but I am very week on GIS projections.
Here is the code to plots the points
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no"/>
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=true"></script>
<script src="../js/d3.v3.min.js"></script>
<style type="text/css">
html, body, #map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.markers {
position: absolute;
}
svg.pts {
position: absolute;
}
.markers border {
position: absolute;
stroke: black;
stroke-width: 2px;
}
.markers svg.pts {
width: 60px;
height: 20px;
padding-right: 100px;
font: 10px sans-serif;
}
.markers circle {
fill: brown;
stroke: black;
stroke-width: 1.5px;
}
.SvgOverlay path {
stroke: Orange;
stroke-width: 2px;
fill: Orange;
fill-opacity: .3;
}
</style>
</head>
<body>
<div id="map"></div>
<script type="text/javascript">
var map = new google.maps.Map(d3.select("#map").node(), {
zoom: 15,
center: new google.maps.LatLng(29.371397, -81.54938), //N/S E/W
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var data = [ //note this is not in GeoJSON format
{name:"pt1",lng:-81.55082967,lat:29.374915304},
{name:"pt2",lng:-81.55211713,lat:29.373504039},
{name:"pt3",lng:-81.5842252,lat:29.417969924},
{name:"pt4",lng:-81.55230021,lat:29.374245073},
{name:"pt5",lng:-81.55115,lat:29.37263},
{name:"pt6",lng:-81.58737814,lat:29.358476912},
{name:"pt7",lng:-81.59230268,lat:29.359308171},
{name:"pt8",lng:-81.58783883,lat:29.356449048},
{name:"pt9",lng:-81.58189168,lat:29.420264027},
{name:"pt10",lng:-81.58288,lat:29.4202},
{name:"pt11",lng:-81.56079477,lat:29.359527893},
{name:"pt12",lng:-81.55861145,lat:29.356670068},
{name:"pt13",lng:-81.57961314,lat:29.420893275},
{name:"pt14",lng:-81.579302,lat:29.419368},
{name:"pt15",lng:-81.55979967,lat:29.359768002},
{name:"pt16",lng:-81.55823261,lat:29.36122515},
{name:"pt17",lng:-81.58189168,lat:29.420264027},
{name:"pt18",lng:-81.57997524,lat:29.421120323},
{name:"pt19",lng:-81.58148399,lat:29.420030491},
{name:"pt20",lng:-81.57839075,lat:29.420766158},
{name:"pt21",lng:-81.57982489,lat:29.42002304},
{name:"pt22",lng:-81.580266,lat:29.420212},
{name:"pt23",lng:-81.5820392,lat:29.42048164},
{name:"pt24",lng:-81.57894731,lat:29.420509033},
{name:"pt25",lng:-81.57819629,lat:29.418834169}
];
var overlay = new google.maps.OverlayView();
overlay.onAdd = function() {
var layer = d3.select(this.getPanes().overlayLayer).append("div")
.attr("height", "100%")
.attr("width", "100%")
.attr("class", "markers")
.attr("id", "layer");
layer[0][0].style.width = "1366px";
layer[0][0].parentNode.style.width = "100%";
layer[0][0].parentNode.style.height = "100%";
layer[0][0].parentNode.parentNode.style.width = "100%";
layer[0][0].parentNode.parentNode.style.height = "100%";
layer[0][0].parentNode.parentNode.parentNode.style.width = "100%";
layer[0][0].parentNode.parentNode.parentNode.style.height = "100%";
layer[0][0].parentNode.parentNode.parentNode.parentNode.style.width = "100%";
layer[0][0].parentNode.parentNode.parentNode.parentNode.style.height = "100%";
// Add points
overlay.draw = function() {
var projection = this.getProjection(),
padding = 10;
var point = layer.selectAll("svg")
.data( data )
.each(transform) // update existing markers
.enter().append("svg:svg")
.each(transform)
.attr("class", "point pts")
// Add marker on points
point.append("svg:circle")
.attr("r", 4.5)
.attr("cx", padding )
.attr("cy", padding );
// Add a label on points
point.append("svg:text")
.attr("x", padding + 7)
.attr("y", padding)
.attr("dy", ".31em")
.text( function(d) {
return d.name; }
);
//Here is where I'd like to add lines connecting the points, in order
//of appearance in the data object
function _projection( lat, lng ) {
e = new google.maps.LatLng( lat, lng );
e = projection.fromLatLngToDivPixel(e);
return [ e.x - padding, e.y - padding]
// return [ e.x, e.y ]
}
function transform(d) {
//console.log(d);
e = _projection( d.lat, d.lng )
return d3.select(this)
.style("left", e[0] + "px")
.style("top", e[1] + "px");
}
};
};
// Bind overlay to the map…
overlay.setMap(map);
</script>
</body>
</html>
And here is a JSFiddle
Suggestions to get the path added via the data object as presented are most appreciated.
Ok, so I took a look at your code and refactored it a bit. But here is a basic working version of a path drawn between the points: http://jsfiddle.net/AJvt4/3/. There is one caveat though, and that is that the overlayPane doesn't expand when the map pans. I'm not too familiar with google maps so not sure how much I can help there. Here's an explanation of the changes made:
First I created a encompassing svg to house all of your d3 elements in the onAdd event:
var svg = layer.append('svg')
.attr('x', 0)
.attr('y', 0)
Also in the onAdd event I added a d3 path generator (you can read more here):
var lineFn = d3.svg.line()
.x(function (d) {
e = _projection(d.lat, d.lng);
return e[0] + padding
})
.y(function (d) {
e = _projection(d.lat, d.lng);
return e[1] + padding
})
To actually draw the line, I added this in the add event handler:
var line = svg.selectAll('.path').data([data])
line.enter().append('path')
line.attr('class', 'path')
.attr('d', lineFn)
It's important to note the array around the data ([data]). This is because d3 expects an array of arrays with each inner array holding points to a line. This makes it easier to draw multiple lines. In your case there is only one line.
You'll notice a few other changes to make the code a bit more d3-esque. Hope that helps get you started!
Related
I've got the following d3 map:
but as you can see it's all bunched up there in the left corner of the screen- this is sub opimal- ideally I would like it to be centered.
How can I achieve this?
I guess it should be easy but every time I google something like "center align d3 map" I get things about zooming
:/
maybe I need to create a div or something?
the code is here on my GitHub
also below- it's pretty well commented.
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<style>
.border {
stroke: #000;
fill: none;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: .5px;
stroke-opacity: .5;
}
div.tooltip {
position: absolute;
text-align: center;
width: 84px;
height: 64px;
padding: 2px;
font: 12px sans-serif;
background: lightgrey;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
</head>
<body>
<h1>Administrative Sub-Regions of Europe</h1>
<!-- this is the form at the bottom to change the NUTS -->
<form>
<select id="json_sources" name="json_sources" >
<option value ="nuts0" selected >Source 0</option>
<option value ="nuts1" >Source 1</option>
<option value ="nuts2" >Source 2</option>
<option value ="nuts3" >Source 3</option>
</select>
<form>
<!-- spinner -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.0.1/spin.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="http://d3js.org/colorbrewer.v1.min.js"></script>
<script src="https://cdn.rawgit.com/rveciana/d3-composite-projections/v0.2.0/composite-projections.min.js"></script>
<!-- why do we need this? -->
<section id='chart'>
</section>
<script>
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var width = 600,
height = 500;
var projection = d3.geo.conicConformalEurope();
var graticule = d3.geo.graticule();
var path = d3.geo.path()
.projection(projection);
// Find new colours here: http://colorbrewer2.org/
var scale = d3.scale
.quantize()
.domain([10,60])
.range(colorbrewer.PuRd[3]);
var svg = d3.select("body")
.append("svg")
.attr("width", width, "100%")
.attr("height", height, "100%")
.call(
d3.
behavior.
zoom().
on("zoom", function () {
svg.attr("transform", "translate(" +
d3.event.translate +
")" + " scale(" +
d3.event.scale +
")")
}
)
)
.on("dblclick.zoom", null)
.append("g")
//what the hell does this do?
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
// pretty self spoken
var dropdown = d3.select("#json_sources")
// config references SPINNER RELATED
var chartConfig = {
target : 'chart',
data_url : './nuts0.json',
width: 600,
height: 500,
val: 90
};
// loader settings SPINNER RELATED
var opts = {
lines: 9, // The number of lines to draw
length: 9, // The length of each line
width: 5, // The line thickness
radius: 14, // The radius of the inner circle
color: '#EE3124', // #rgb or #rrggbb or array of colors
speed: 1.9, // Rounds per second
trail: 40, // Afterglow percentage
className: 'spinner', // The CSS class to assign to the spinner
};
// SPINNER RELATED
var target = document.getElementById(chartConfig.target);
// KICK OFF callback function wrapped for loader in 'init' function
function init() {
// trigger loader initial spinner
var spinner = new Spinner(opts).spin(target);
// load json data and trigger callback
d3.json(chartConfig.data_url, function(data) {
// stop spin.js loader
spinner.stop();
// instantiate chart within callback
chart(data);
});
}
//call that init function we define above
init();
//here where all the real stuff happens
//in fact all that init stuff is just legacy
//from the spinner example
function chart(data) {
//start of map making function
var change = function() {
// trigger loader of the spinner
var spinner = new Spinner(opts).spin(target);
// did they change the NUTS?
var source = dropdown.node().options[dropdown.node().selectedIndex].value;
//necessary data processing
var str1 = source;
var str2 = ".json";
var file = str1.concat(str2);
console.log(file);
d3.json(file, function(error, europe) {
d3.csv("povertry_rate.csv", function(error, povrate) {
//change the map to apadpt to the nuts file
if (source == "nuts1") {
var land = topojson.feature(europe, europe.objects.nuts1);
} else if (source == "nuts2") {
var land = topojson.feature(europe, europe.objects.nuts2);
} else if (source == "nuts3") {
var land = topojson.feature(europe, europe.objects.nuts3);
} else if (source == "nuts0") {
var land = topojson.feature(europe, europe.objects.nuts0);
}
data = {};
povrate.forEach(function(d) {
data[d.GEO] = d['2013'];
});
//clear way for the regeneration
d3.selectAll("path").remove();
//recreate those map lines
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
// stop spin.js loader
spinner.stop();
console.info(data);
svg
.selectAll("path")
.data(land.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke","#000")
.style("stroke-width",".5px")
.style("fill",function(d){
var value = data[d.id];
if (isNaN(value)){
value = data[d.id.substring(0,2)];
}
if (isNaN(value)){
return "#fff";
}
return scale(value);
})
.on("mouseover", function(d,i) {
var value = data[d.id];
if (isNaN(value)){
value = data[d.id.substring(0,2)];
}
div.transition()
.duration(200)
.style("opacity", 0.9);
div.html("<b>"+d.properties.name+"</b><br/>" + value + "%")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d,i) {
div.transition()
.duration(500)
.style("opacity", 0);
});
svg
.append("path")
.style("fill","none")
.style("stroke","#000")
.attr("d", projection.getCompositionBorders());
});
})
}
dropdown.on("change", change)
change(); //call that change function once
}
</script>
</body>
</html>
Not really a d3 question just a little CSS:
svg {
display: block;
margin: auto;
border: 1px solid gray;
}
I'm following the canonical "Let’s Make a Map" tutorial- but to spice things up I'm melding it with one about Germany- so I'm working with slightly different data.
Things are so far working out- barring this minor hiccup- but now I've come to the section "#Displaying Places" which is where you're supposed to show the names of the cities on the map.
The problem is happening in the following line:
.text(function(d) {
if (d.properties.name!=="Berlin" &&
d.properties.name!=="Bremen"){
//for some reason this is undefined
console.log(d.properties.name);
return d.properties.name;
}
})
The value of that console.log(d.properties.name); is always undefined and I can't figure out why!
I suppose it's because name is out of scope for d- but I don't know how to fix it. Is that right? If so- how to fix it? if not- what is the real problem?
Here is what my code looks like- it's pretty concise:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.subunit{fill:#fff;}
.subunit.Nordrhein-Westfalen{ fill: #aba; }
.subunit.Baden-Württemberg{ fill: #bab; }
.subunit.Hessen{ fill: #bcb; }
.subunit.Niedersachsen{ fill: #cbc; }
.subunit.Thüringen{ fill: #cdc; }
.subunit.Hamburg{ fill: #dcd; }
.subunit.Schleswig-Holstein{ fill: #ded; }
.subunit.Rheinland-Pfalz{ fill: #ede; }
.subunit.Saarland{ fill: #efe; }
.subunit.Sachsen-Anhalt{ fill: #fef; }
.subunit.Brandenburg{ fill: #aaa; }
.subunit.Mecklenburg-Vorpommern{ fill: #bbb; }
.subunit.Bayern { fill: #ccc; }
.subunit.Sachsen { fill: #ddd; }
.subunit.Bremen { fill: #eee; }
.subunit.Berlin { fill: #fff; }
.subunit-boundary {
fill: none;
stroke: #777;
stroke-dasharray: 2,2;
stroke-linejoin: round;
}
.place,
.place-label {
fill: #444;
font-size:14px;
}
text {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 20px;
pointer-events: none;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 1160;
var projection = d3.geo.mercator()
.center([10.5, 51.35])
.scale(3000)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("de.json", function(error, de) {
//colouring the different subunits
svg.selectAll(".subunit")
.data(topojson.feature(de, de.objects.subunits).features)
.enter().append("path")
.attr("class", function(d) {
// console.log(d.properties.name);
return "subunit " + d.properties.name;
})
.attr("d", path);
//adding a border to the states
svg.append("path")
.datum(topojson.mesh(de, de.objects.subunits, function(a,b) {
if (a!==b ||
a.properties.name === "Berlin"||
a.properties.name === "Bremen"){
var ret = a;
}
return ret;
}))
.attr("d", path)
.attr("class", "subunit-boundary");
// add small black dots for populated places
svg.append("path")
.datum(topojson.feature(de, de.objects.places))
.attr("d", path)
.attr("class", "place");
//trying to display names of cities
svg.selectAll(".place-label")
.data(topojson.feature(de, de.objects.places).features)
.enter().append("text")
.attr("class", "place-label")
.attr("transform", function(d) {
//small test
//console.log( "translate(" + projection(d.geometry.coordinates) + ")" );
return "translate(" + projection(d.geometry.coordinates) + ")";
})
.attr("dy", ".35em")
.text(function(d) {
if (d.properties.name!=="Berlin" &&
d.properties.name!=="Bremen"){
//for some reason this is undefined
console.log(d.properties.name);
return d.properties.name;
}
})
.attr("x", function(d) {
return d.geometry.coordinates[0] > -1 ? 6 : -6;
})
.style("text-anchor", function(d) {
return d.geometry.coordinates[0] > -1 ? "start" : "end";
});
});
</script>
Here is the data file.
EDIT
expected
actual
Inside your .topojson you have two section:
properties: names of your counties and polygons
places: coordinates of the points
You access the first collection with:
de.objects.subunits
And the second collection through:
de.subunits.places
After file is loaded sepearte into two different variables to use it:
d3.json("de.json", function(error, de) {
var counti = topojson.feature(de, de.objects.subunits)
var places = topojson.feature(de, de.objects.places)
then referenciate the content adding .features
.data(counti.features) // <-- to draw your paths and get the .name: München
or
.data(places.features) // <-- to draw the circles for the cities: "coordinates": [11.573039376427117, 48.131688134368815]
Mike's topojson has:
{
"type": "Feature",
"properties": {
"name": "Ayr"
},
"geometry": {
"type": "Point",
"coordinates": [
-4.617021378468872,
55.44930882146421
]
}
and you has:
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
11.573039376427117,
48.131688134368815
]
}
Mike's point properties looks like this:
and point coordinates
Your point properties:
Solution:
The right way
Open your map on GIS software (ArcGIS-pay, Q-GIS-free) edit and correct paths and points properties and export as TopoJSON again.-
Easy way
Go to: geojson.io load your json and add propertie name to your point (16 points, easy cake) and save as TopoJSON again.-
Now you're there correct countie info deleting one column (you has duplicate info)
#Klaujesi has explained the reason pretty well.
I am just going to solve the issue by a work around.
Since there is no property inside the Feature, so you can get the property from de.objects.subunits, like below.
.text(function(d, i) {
//here i is the index of the place.
//de.objects.subunits.geometries[i].properties will give you the name that you are looking for.
d.properties = de.objects.subunits.geometries[i].properties;
if (d.properties.name!=="Berlin" &&
d.properties.name!=="Bremen"){
//for some reason this is undefined
console.log(d);
return d.properties.name;
}
})
working code here
I am using a modified version of D3 + Google Maps, wherein, I'd like a modal to pop up when I click on an SVG circle.
Here's the JavaScript:
var now = new Date();
var hour = now.getHours();
// Create the Google Map…
var map = new google.maps.Map(d3.select("#map").node(), {
zoom: 16,
center: new google.maps.LatLng(19.134249, 72.913608),
mapTypeId: google.maps.MapTypeId.TERRAIN
});
function assign_dots() {
$("#hour").text(hour);
// Load the station data. When the data comes back, create an overlay.
d3.json("stations.json", function (error, data) {
var overlay = new google.maps.OverlayView();
if (error) throw error;
// 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;
layer.selectAll("svg").remove();
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", function(d){
return d.value[3][hour]*6;
})
.attr("cx", padding)
.attr("cy", padding)
.on("click", toggleExpand);
// Add a label.
marker.append("text")
.attr("x", padding + 10)
.attr("y", padding)
.attr("dy", ".31em")
.text(function (d) {
if (d.value[3][hour])
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");
}
function toggleExpand(d) {
$('#myModal').modal('toggle');
console.log(d.value[2]);
console.log("Reaches here");
}
};
};
overlay.onRemove = function() {};
// Bind our overlay to the map
overlay.setMap(map);
});
}
window.onload = assign_dots();
function progress_time() {
hour = hour + 1;
if (hour == 24) hour = 0;
console.log(hour);
d3.selectAll("svg").remove();
assign_dots();
}
function regress_time() {
hour = hour - 1;
if (hour == -1) hour = 23;
console.log(hour);
d3.selectAll("svg").remove();
assign_dots();
}
Basically, in the assign_dots function, when I am appending a circle to the marker, it should set an onclick function to be toggleExpand, but when I am clicking on an svg circle, nothing happens.
What can I be missing?
Edit
Here's the stylesheet:
html, body, #map {
width: 100%;
height: 90%;
margin: 0;
padding: 10px;
}
#map {
border: solid;
}
.stations, .stations svg {
position: absolute;
}
.stations svg {
width: 60px;
height: 20px;
padding-right: 100px;
font: 10px sans-serif;
}
.stations circle {
fill: blue;
stroke: black;
stroke-width: 2px;
}
And I have also included bootstrap.
For some reason, when I remove the bootstrap css, the on click starts working. What could the problem be?
I am new to d3.js ,I made reference this example: http://bl.ocks.org/emeeks/4531633 .
I changed the example's map to Google map ,and want to use SVG to draw bar
chart on the Google map.
<!DOCTYPE html>
<title>test</title>
<meta charset="utf-8">
<style type="text/css">
.gmap{ display: block; width: 1000px; height: 800px; }
.stations, .stations svg { position: absolute; }
.stations svg { width: 120px; height: 30px; padding-right: 100px; font: 12px sans-serif; }
.stations circle { fill: yellow; stroke: black; stroke-width: 1.5px; }
</style>
<body>
<div class="gmap" id="map-canvas"></div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://maps.googleapis.com/maps/api/js"></script>
<script></script>
<script>
var map;
function initialize() {
var mapOptions = { zoom: 8, center: new
google.maps.LatLng(23.7147979,120.7105502) };
map = new google.maps.Map( document.getElementById('map-canvas') , mapOptions);
}
initialize();
var width = 960, height = 960;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.csv("223-loc.csv", function(data) {
var parse = d3.time.format("%Y/%m/%d").parse;
aaa = d3.nest()
.key(function(d){ return d.birdName;})
.entries(stocks = data);
aaa_copy = d3.nest()
.key(function(d){ return d.birdName;})
.entries(stocks = data);
var position2keep= new Array();
var i=0;
aaa.forEach(function(s)
{
for (nn=0; nn<selectAryy.length; nn++)
{
if (s.key == selectAryy[nn])
{ position2keep.push(i); break; }
}
i++;
});
position2keep.sort();
for (j=aaa_copy.length-1; j>=0; j--)
{
if ( position2keep.indexOf(j) == -1)
aaa_copy.splice(j,1);
}
aaa_copy.forEach(function(s) {
s.values.forEach(function(d) {
for (nn=0; nn<selectAryy.length; nn++){
if (d.birdName== selectAryy[nn]){
d.date = parse(d.date);
d.count = +d.count;
d.lat = +d.lat;
d.lng = +d.lng;
}
}
bars = svg.selectAll("g")
.data(s)
.enter()
.append("g")
.attr("class", "bars")
.attr("transform", function(d) {return "translate("+ d.lat +","+
d.lng+")";});
bars.append("rect")
.attr('height', function(d) {return d.count*1000})
.attr('width', 10)
.attr('y', function(d) {return -(d.count)})
.attr("class", "bars")
.style("fill", "green");
bars.append("text")
.text(function(d) {return d.location})
.attr("x", -10)
.attr("y", 18);
bars.setMap(map);
});
});
});
</script>
</body>
My CSV data: https://drive.google.com/open?id=0B6SUWnrBmDwSWkI4bVNtOTNSOTA
I use d3.csv load data,it works.
But When I want to put the data into SVG to draw bar chart,it didn't work.
Can anyone help me to fix it?
In the provided example there are some issues:
selectAryy is not defined
and most importantly bars.setMap(map); does not seem valid. Do you mean setMap function of google.maps.OverlayView object?
In order to create a bar chart on Google Maps i would recommend to implement it as Custom Overlay.
Having said that the below example demonstrates how to add svg objects (bar chart) into Google Maps using overlay technique:
Example
function BarChartOverlay(chartData, map){
this.map_ = map;
this.chartData_ = chartData;
this.div_=null;
this.setMap(map);
}
BarChartOverlay.prototype = new google.maps.OverlayView();
BarChartOverlay.prototype.onAdd = function(){
var overlayProjection = this.getProjection();
var div = document.createElement('div');
div.setAttribute('id','chartDiv');
var chartArea = d3.select(div).append("svg");
this.chartData_.forEach(function(item){
var pos = overlayProjection.fromLatLngToDivPixel(new google.maps.LatLng(item[0], item[1]));
var bar = chartArea
.append("rect")
.attr("x", pos.x)
.attr("y", pos.y)
.attr("width", 40)
.attr("height", item[2])
.attr("fill-opacity", '0.5')
.attr("fill", 'purple');
});
this.div_ = div;
this.chartArea_ = chartArea;
var panes = this.getPanes();
panes.overlayLayer.appendChild(div);
};
BarChartOverlay.prototype.draw = function(){
var overlayProjection = this.getProjection();
var sw = overlayProjection.fromLatLngToDivPixel(this.map_.getBounds().getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.map_.getBounds().getNorthEast());
var chartAreaSize = sw.x + ' ' + ne.y + ' ' + (ne.x - sw.x) + ' ' + (sw.y - ne.y);
this.chartArea_.attr('viewBox',chartAreaSize);
};
BarChartOverlay.prototype.onRemove = function(){
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
};
function initialize() {
var mapOptions = {
zoom: 8, center: new
google.maps.LatLng(23.7147979, 120.7105502)
};
var chartData = [
[25.204757,121.6896172,100],
[22.7972447,121.0713702,130],
[24.254972,120.6011066,80]
];
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var overlay = new BarChartOverlay(chartData, map);
}
initialize();
.gmap {
display: block;
width: 1000px;
height: 800px;
}
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://maps.googleapis.com/maps/api/js"></script>
<div class="gmap" id="map-canvas"></div>
I am new to JavaScript and coding in general and I have run into an issue trying to resolve a problem with a JSON column. Los Angeles operates a very handy data portal (https://data.lacity.org/A-Safe-City/LAPD-Crime-and-Collision-Raw-Data-2014/eta5-h8qx), the endpoint of which I have been pointing to in my script. However, the lat/long (found under location_1 column at the above link) is in the format (34.0000, -118.0000). I am attempting to make heat maps using this crime data but I need the lat/long values individually to assign as parameters for the library I am attempting to use. Can someone show me how I can separate those values and assign them to individual variables? Here is an excerpt from the script I am borrowing but I would be happy to show the entire thing if you all think it would help.
//let Socrata do the sorting:
http://data.lacity.org/resource/eta5-h8qx.json
var sodaUrl = "http://data.lacity.org/resource/eta5-h8qx.json?crm_cd=330"
//get json from the Socrata API
$.getJSON( sodaUrl, function( rawData ) {
var goodData = [];
for(var i=0;i<rawData.length;i++){
rawData[i].value = 0;
rawData[i].fresh=true;
if(rawData[i].location_1) {
rawData[i].lat = parseFloat(rawData[i].location_1.latitude);
rawData[i].lng = parseFloat(rawData[i].location_1.longitude);
}
rawData[i].date = new Date(rawData[i].date_occ + "-04:00");
if(rawData[i].location_1) {
if (rawData[i].location_1.latitude && rawData[i].location_1.longitude) {
goodData.push(rawData[i]);
};
};
};
Any help is greatly appreciated and I am happy to be more explicit if I have done poorly thus far.
Edit: here is the whole HTML document. When I try to preview in Dreamweaver I only get the basemap.
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>PGC Crime Heatmap Test</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<style>
body {
margin:0; padding:0;
color:#fff;
font-family:verdana,sans-serif;
}
#map {
position:absolute; top:0; bottom:0; width:100%;
}
#titleBox {
position: absolute;
height: 50px;
width: 800px;
background: #666;
border-radius: 10px;
right: 0;
left: 0;
top: 20px;
margin: 0 auto;
border: 2px solid #AFAFAF;
font-size: 17px;
font-weight: bold;
line-height: 46px;
text-align: center;
}
#chartBox {
position:absolute;
height:100px;
width:800px;
background:#666;
border-radius:10px;
right:0;
left:0;
bottom:20px;
margin: 0 auto;
border: 2px solid #AFAFAF;
}
svg text {
fill: #fff;
}
svg line {
stroke: #fff;
stroke-width: 1px;
}
svg .domain {
stroke: #fff;
fill: none;
}
svg rect {
fill: #00AEF7;
opacity: .4;
stroke: #FFF;
}
</style>
</head>
<body>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<script src='heatmap.js'></script>
<script src='leaflet-heatmap.js'></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id = 'map'></div>
<div id = 'titleBox'>
Zip Code 11201 Vehicle Collisions Animated Heatmap (September 1 - Now)
</div>
<div id = 'chartBox'></div>
<!-- Example data. -->
<script>
//heatmap.js config
var cfg = {
// radius should be small ONLY if scaleRadius is true (or small radius is intended)
// if scaleRadius is false it will be the constant radius used in pixels
"radius": .001,
"maxOpacity": 1,
// scales the radius based on map zoom
"scaleRadius": true,
// if set to false the heatmap uses the global maximum for colorization
// if activated: uses the data maximum within the current map boundaries
// (there will always be a red spot with useLocalExtremas true)
"useLocalExtrema": false,
// which field name in your data represents the latitude - default "lat"
//latField: 'latitude',
// which field name in your data represents the longitude - default "lng"
//lngField: 'longitude',
// which field name in your data represents the data value - default "value"
valueField: 'value'
};
var heatmapLayer = new HeatmapOverlay(cfg);
var baseLayer = L.tileLayer(
'https://{s}.tiles.mapbox.com/v3/cwhong.map-hziyh867/{z}/{x}/{y}.png',{
attribution: "<a href='https://www.mapbox.com/about/maps/' target='_blank'>© Mapbox © OpenStreetMap</a> <a class='mapbox-improve-map' href='https://www.mapbox.com/map-feedback/' target='_blank'>Improve this map</a>",
maxZoom: 18
}
);
//setup map and add layers
var map = new L.Map('map', {
center: new L.LatLng(34.0224, -118.2870),
zoom: 14,
layers: [baseLayer, heatmapLayer]
});
//let Socrata do the sorting:
http://data.lacity.org/resource/eta5-h8qx.json
var sodaUrl = "http://data.lacity.org/resource/eta5-h8qx.json?crm_cd=330"
//get json from the Socrata API
$.getJSON( sodaUrl, function( rawData ) {
var goodData = [];
for(var i=0;i<rawData.length;i++){
rawData[i].value = 0;
rawData[i].fresh=true;
if(rawData[i].location_1) {
rawData[i].lat = parseFloat(rawData[i].location_1.latitude);
rawData[i].lng = parseFloat(rawData[i].location_1.longitude);
}
rawData[i].date = new Date(rawData[i].date_occ + "-04:00");
if(rawData[i].location_1) {
if (rawData[i].location_1.latitude && rawData[i].location_1.longitude) {
goodData.push(rawData[i]);
};
};
};
console.log(goodData);
var nextDate = new Date(goodData[0].date);
console.log("first nextDate: " + nextDate);
//initilaize variables for the D3 chart
var countArray = [],
svg,
day,
x,
y,
margin,
height,
width,
intervalCounter = 10,
index = 0,
lastDate,
data = {
max:15,
min:0,
data:[]
};
initializeChart();
//iterate
setInterval(function () {
//iterates 10 times for each day
if (intervalCounter == 10){
intervalCounter = 0;
getAnotherDay();
} else {
intervalCounter++;
}
//create new array for live points, push it to the map
var newData = [];
for(var j=0;j<data.data.length;j++) {
var point = data.data[j];
if(point.value >= 10) {
point.fresh = false;
}
//fade in fresh points, fade out unfresh points
if(point.fresh) {
point.value = point.value + .8;
} else {
point.value = point.value - .1;
}
if(point.value > 0) {
newData.push(data.data[j]);
}
}
data.data = newData;
heatmapLayer.setData(data);
//update the chart
day = svg.selectAll(".day")
.data(countArray)
.enter()
.append("g")
.attr("class", "day")
.attr("transform", function (d) {
//var yesterday = new Date(d.date);
//yesterday = yesterday.setDate(yesterday.getDate() - 1)
return "translate(" + x(d.date) + ",0)";
})
.append("rect")
.attr("width", 28)
.attr("y", function (d) {
return height - y(d.count);
})
.attr("height", function (d) {
return y(d.count);
})
.attr("class", function (d) {
return (d.date);
})
}, 100);
function getAnotherDay() {
nextDate = new Date(nextDate.setHours(24,0,0,0));
var todayCounter = 0;
//iterate over goodData, push today's events to data.data
for (;;index++) {
var thisDate = goodData[index].date;
console.log(thisDate + nextDate);
if(thisDate.getTime() < nextDate.getTime()) {
data.data.push(goodData[index]);
todayCounter++;
lastDate = thisDate;
} else {
//Still need to increment lastDate if there is no data
if(todayCounter == 0) {
console.log(lastDate);
lastDate = lastDate.getDate() - 1;
}
var todayCount = {
date:lastDate,
count:todayCounter
};
countArray.push(todayCount);
break;
}
}
}
//sets margins and axes for the D3 chart. Borrowed from Chris Metcalf's example on dev.socrata.com
function initializeChart() {
// Set our margins
margin = {
top: 20,
right: 20,
bottom: 30,
left: 60
},
width = 800 - margin.left - margin.right,
height = 100 - margin.top - margin.bottom;
// Our X scale
x = d3.time.scale()
.domain([new Date(goodData[0].date), d3.time.day.offset(new Date(goodData[goodData.length - 1].date), 1)])
.rangeRound([0, width - margin.left - margin.right])
//.ticks(d3.time.day, 1);
// Our Y scale
y = d3.scale.linear()
.domain([0,100])
.rangeRound([height, 0]);
// Our color bands
var color = d3.scale.ordinal()
.range(["#308fef", "#5fa9f3", "#1176db"]);
// Use our X scale to set a bottom axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
// Same for our left axis
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickValues([0,50,100]);
// Add our chart to the #chart div
svg = d3.select("#chartBox").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("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
};
});
</script>
</body>
</html>
It should function exactly like this site, I am merely using a different dataportal and mine is centered on LA. http://chriswhong.github.io/nyc-heatmap/