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/
Related
I have a dashboard with two separate maps of a state showing different data based on years 2014 and 2012. The map when hovered over show the name of area individually. What I need to do is display both 2012 and 2014 maps's tooltips at the same time over the respective maps when I mouseover any one of the two maps. How can I display both at the same time. I would appreciate any help with this. Thanks.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test dashboard</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" >
<style>
#gujarat-viz-2017, #buttons {
border-right: 1px solid #ccc
}
.container {
background-color: #d5e8ec;
}
.const0 {
display: none;
}
.emptyparty {
fill:#f9f9f1;
}
.emptyparty:hover, .constituency:hover {
fill:#ccc;
}
.hidden { display: none; }
.showtooltip { position: absolute; z-index: 10000; background-color: #333;
border-radius: 10px; color: #fff; padding: 5px; }
/*Party colors*/
.bjp{ fill: #f88101;}
.inc{ fill: #6da736;}
.ncp{ fill: #076598;}
.gpp{ fill: #5a469d;}
.ind{ fill: #25a29a;}
.jdu{ fill: #eb4d4c;}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div id="gujarat-viz-2014" class="col-md-6">
<h2>2014</h2>
</div>
<div id="gujarat-viz-2012" class="col-md-6">
<h2>2012</h2>
</div>
</div> <!-- .row -->
</div>
<script src="http://www.thehindu.com/static/js/jquery-1.10.2.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script>
function map_function(map_settings){
// Global variables
var margin = { top: 50, left:50, right:50, bottom:50 },
height = 400 - margin.top - margin.bottom,
width = 500 - margin.left - margin.right;
// Create SVG canvas with responsive resizing
var svg = d3.select(map_settings["htmlelement"])
.append("svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMinYMin")
.append("g")
.attr("class", "data"+map_settings["year"])
// Add a tooltip to visualization
var tooltip = d3.select('body').append('div')
.attr('class', 'hidden showtooltip')
.attr('id', "tooltip"+map_settings["year"])
// queue and read the topojson, json data file
d3.queue()
.defer(d3.json, "https://api.myjson.com/bins/17m3if")
.defer(d3.json, map_settings.data)
.await(render_map)
var projection = d3.geoMercator()
.scale(3000)
.center([71.5, 22.3])
.translate([width / 2, height / 2])
var geoPath = d3.geoPath()
.projection(projection)
function render_map(error, mapshape, mapdata){
var constituency = topojson.feature(mapshape, mapshape.objects.collection).features;
dataMap = {};
mapdata.forEach(function(d){
dataMap[d.constNo] = d;
})
var fill_function = function(d) {
// d3.select(this).attr('fill', "white")
} // end of mousemove_function
var mousemove_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
// console.log(constinfo.constituencyName)
// console.log(d3.select(this).data()[0].properties)
var html = "<p>"+constinfo.constituencyName+"</p>"
tooltip.classed('hidden', false)
.html(html)
.style("left", (d3.event.clientX - 10) + "px")
.style("top", (d3.event.clientY - 45) + "px");
} // end of mousemove_function
var class_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
var className = "constituency ";
if(constinfo !== undefined) {
className += ("c"+constinfo.constNo+" ")
className += constinfo.leadingParty.replace(/[^a-zA-Z ]/g, "").toLowerCase()
} else {
className += "emptyparty"
className += " const"
className += d.properties.AC_NO
}
return className;
} // end of class_function
var mouseout_function = function(d) {
tooltip.classed('hidden', true)
} // end of mousemove_function
svg.selectAll(".constituency")
.data(constituency)
.enter().append("path")
.attr("d", geoPath)
.attr('class', class_function)
.attr('fill', "white")
.attr('stroke', "#e8e8e8")
.attr('stroke-width', "0.5")
.on('mouseover', mousemove_function)
.on('mouseout', mouseout_function)
} // render_map
} // map_function
var gujarat_data_2014 = {
htmlelement: "#gujarat-viz-2014",
data: "https://api.myjson.com/bins/yolfr",
year: "2014"
};
var gujarat_data_2012 = {
htmlelement: "#gujarat-viz-2012",
data: "https://api.myjson.com/bins/19ztxj",
year: "2012"
};
map_function(gujarat_data_2014);
map_function(gujarat_data_2012);
</script>
</body>
</html>
I'd modify your mousemove and mouseout to operate on both maps at the same time:
var mousemove_function = function(d) {
var constinfo = dataMap[d.properties.AC_NO];
var html = "<p>" + constinfo.constituencyName + "</p>"
var tooltips = d3.selectAll('.showtooltip');
// get paths from all maps
d3.selectAll('.c' + constinfo.constNo)
.each(function(d,i){
var pos = this.getBoundingClientRect();
// operate on appropriate tooltip
d3.select(tooltips.nodes()[i]).classed('hidden', false)
.html(html)
.style("left", (pos.x + pos.width/2) + "px")
.style("top", (pos.y - pos.height/2) + "px");
});
} // end of mousemove_function
var mouseout_function = function(d) {
d3.selectAll('.showtooltip').classed('hidden', true);
} // end of mousemove_function
Running code here.
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 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?
Hey Im' currently experimenting with this D3 Example and I want to implement my own slider which is showing data of a json File (I included everything in a github repo because I don't know how to show you my working files and not spend to much space for it - especially the json file. Any tips for the future?). So basically I have my bubble.html:
<!DOCTYPE html>
<head>
<title>D3 Mapping Timeline</title>
<meta charset="utf-8">
<link rel="stylesheet" href="d3.slider.css" />
<style>
path {
fill: none;
stroke: #333;
stroke-width: .5px;
}
.land-boundary {
stroke-width: 1px;
}
.county-boundary {
stroke: #ddd;
}
.site {
stroke-width: .5px;
stroke: #333;
fill: #9cf;
}
#slider3 {
margin: 20px 0 10px 20px;
width: 900px;
}
</style>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="d3.slider.js"></script>
</head>
<body>
<div id="slider3"></div>
<script>
var width = 1240,
height = 720;
var projection = d3.geo.mercator()
.translate([width / 2, height / 2])
.scale((width - 1) / 2 / Math.PI);
d3.json("vorfaelle.json", function(error, data){
console.log(data.features[1].geometry.coordinates);
window.site_data = data;
});
var displaySites = function(data) {
var sites = svg.selectAll(".site")
.data(data);
sites.enter().append("circle")
.attr("class", "site")
.attr("cx", function(d) {
for (var i = 0; i < d.features.length+1; i++) {
console.log(d.features[i].geometry.coordinates[0]);
return projection(d.features[i].geometry.coordinates[0])
//return projection([d.lng, d.lat])[0];
}
})
.attr("cy", function(d) {
for (var i = 0; i < d.features.length+1; i++) {
console.log(d.features[i].geometry.coordinates[1]);
return projection([d.features[i].geometry.coordinates[1]])
//return projection([d.lng, d.lat])[0];
}
})
.attr("r", 1)
.transition().duration(400)
.attr("r", 5);
sites.exit()
.transition().duration(200)
.attr("r",1)
.remove();
};
// var minDateUnix = moment('2014-07-01', "YYYY MM DD").unix();
// var maxDateUnix = moment('2015-07-21', "YYYY MM DD").unix();
var dateParser = d3.time.format("%d.%m.%Y").parse;
var minDate = dateParser("01.01.2015");
var maxDate = dateParser("31.12.2015");
console.log(minDate);
var secondsInDay = 60 * 60 * 24;
d3.select('#slider3').call(d3.slider()
.axis(true).min(minDate).max(maxDate).step(1)
.on("slide", function(evt, value) {
var newData = _(site_data).filter( function(site) {
console.log(site)
// for (var i = 0; i < site.features.length; i++) {
// return site.features[i].properties.date < value;
// }
})
console.log("New set size ", newData.length);
displaySites(newData);
})
);
</script>
</body>
which is getting the data from the json (on my repo vorfaelle.json). Now when I move the slider handle it "crashes" and gives me this error:
What's exactly wrong? Is it because i did not read the data properly?
Add the svg element to the dom and assign it to a variable so that you can use it your code.
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
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!