Placing Lat/Long on a azimuthalEqualArea map with D3 - javascript

I'm trying to plot some lat/long points onto a map, but I can't get them to appear in the correct place.
The dots should be in San Francisco. I have a JSfiddle of the code.
var width = 400,
height = 400;
var projection = d3.geo.azimuthalEqualArea()
.clipAngle(180 - 1e-3)
.scale(100)
.translate([width / 2, height / 2])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("defs").append("path")
.datum({type: "Sphere"})
.attr("id", "sphere")
.attr("d", path);
svg.append("use")
.attr("class", "stroke")
.attr("xlink:href", "#sphere");
svg.append("use")
.attr("class", "fill")
.attr("xlink:href", "#sphere");
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("http://bl.ocks.org/mbostock/raw/4090846/world-50m.json", function(error, world) {
svg.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
var latlong = {"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838312,-122.0423922]},"properties":{"timestampMs":1415894666875}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838474,-122.0423972]},"properties":{"timestampMs":1415894601718}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838474,-122.0423972]},"properties":{"timestampMs":1415894536288}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838411,-122.0424015]},"properties":{"timestampMs":1415894471356}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.383878,-122.0423925]},"properties":{"timestampMs":1415894406257}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838317,-122.0423856]},"properties":{"timestampMs":1415894326769}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838287,-122.0423933]},"properties":{"timestampMs":1415894261810}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.383829,-122.0423847]},"properties":{"timestampMs":1415894196224}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838765,-122.0424141]},"properties":{"timestampMs":1415894131768}},{"type":"Feature","geometry":{"type":"Point","coordinates":[37.3838177,-122.0423668]},"properties":{"timestampMs":1415894066809}}]};
// THESE ARE THE POINTS THAT ARE NOT BEING PLACED CORRECTLY
svg.selectAll("circle")
.data(latlong.features).enter()
.append("circle")
.attr("cx", function (d) { return projection(d.geometry.coordinates)[1]; })
.attr("cy", function (d) { return projection(d.geometry.coordinates)[0]; })
.attr("r", "2px")
.attr("fill", "red");

You've specified the latitude and longitude the wrong way round for d3.geo and you're also taking the output the wrong way round. It is counter to the way that they are displayed by convention (N/S then E/W) but it is more consistent with a drawing convention of across then up/down.
From path.projection in the D3 API Geo reference:
A projection function takes a two-element array of numbers
representing the coordinates of a location, [longitude, latitude], and
returns a similar two-element array of numbers representing the
projected pixel position [x, y].
To fix this, I've reversed the coordinates of your FeatureCollection:
var latlong = {"type":"FeatureCollection","features":[
{"type":"Feature","geometry":{"type":"Point","coordinates":[-122.0423922,37.3838312]},"properties":{"timestampMs":1415894666875}},
etc...
and reversed the coordinates of your plot.
.attr("cx", function (d) { return projection(d.geometry.coordinates)[0]; })
.attr("cy", function (d) { return projection(d.geometry.coordinates)[1]; })
Everything else was fine. Amended JSFiddle here. So often it's the little things!

Related

D3 script failing to create points in the canvas after a certain limit

I am trying to draw check in information in a d3 canvas. I'm using this popular script to create the map and draw the points.
I can draw roughly 12000 points, after that the script refuses to draw anything on the canvas. Could someone point out what I might be doing wrong?
<script>
var width = 960,
height = 480;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geo.equirectangular()
.scale(153)
.translate([width/2,height/2])
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule();
geo_data = [];
d3.csv("data/2008.csv", function(x){
console.log(x.length)
for (i = 12000; i < 24000; i++) {
geo_data.push([x[i].lat, x[i].long])
}
});
d3.json("https://gist.githubusercontent.com/abenrob/787723ca91772591b47e/raw/8a7f176072d508218e120773943b595c998991be/world-50m.json", function(error, world) {
svg.append("g")
.attr("class", "land")
.selectAll("path")
.data([topojson.object(world, world.objects.land)])
.enter().append("path")
.attr("d", path);
svg.append("g")
.attr("class", "boundary")
.selectAll("boundary")
.data([topojson.object(world, world.objects.countries)])
.enter().append("path")
.attr("d", path);
svg.append("g")
.attr("class", "graticule")
.selectAll("path")
.data(graticule.lines)
.enter().append("path")
.attr("d", path);
svg.selectAll("circle")
.data(geo_data)
.enter()
.append("circle")
.attr("cx", function (d) { return projection(d)[0]; })
.attr("cy", function (d) { return projection(d)[1]; })
.attr("r", "2px")
.attr("fill", "red")
});
</script>
The csv file contains information in this format
lat,long
-104.934812, 39.711152
-104.984703, 39.739154
-105.09543, 39.802002

D3 Zooming and Pan not working for entire map, just the projection

Hopefully this is the right place for this. This is my first time working in d3 and I'm tracking some paths across latitudes (.bars) on a mercator projection map of the world.
While I'm nowhere near where I want to be with this map (trying to animate change over time, and then include tooltips to the .bars): I'm stuck with the zoom feature. I've gotten it to change the scale of the projection when zooming, but it has no effect on my rectangle shapes (.bars). Could someone take a look and let me know what's going on? Why are the rectangles not scaling on zoom? Am I not correctly accessing them in my zoom function?
var width = 960,
height = 550,
scale0 = (width - 1) / 2 / Math.PI;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
// .append("g");
var g = svg.append("g");
var bars = svg.append("rect");
var zoom = d3.behavior.zoom()
.translate([width / 2, height / 2])
.scale(scale0)
.scaleExtent([scale0, 8 * scale0])
.on("zoom", zoomed);
var color = d3.scale.quantize() // Takes data value inputs and will return colors
.range(['rgb(215,48,39)','rgb(244,109,67)','rgb(253,174,97)','rgb(254,224,144)','rgb(255,255,191)','rgb(224,243,248)','rgb(171,217,233)','rgb(116,173,209)','rgb(69,117,180)']);
var projection = d3.geo.mercator()
.scale((width + 1) / 2 / Math.PI)
.translate([width / 2, height / 2])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
svg
.call(zoom)
.call(zoom.event);
d3.json("world-110m.json", function(error, world) {
if (error) throw error;
g.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
g.insert("path", ".graticule")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
d3.csv("data/apr_dove.csv", function(data) {
// Returns value based on min/max of data set to Colorbrewer colors
color.domain([
d3.max(data, function(d) { return d.average_revisit; }),
d3.min(data, function(d) { return d.average_revisit; })
]);
// Defining the rectangle's attributes by monthly data of satellites (latitude and average revisit_rate)
g.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", width)
.attr("class", "bars")
.attr("height", function(d) {
// console.log(projection.translate([0, 0.5])[1])
return projection([0, d.latitude - 0.5])[1] - projection([0, d.latitude])[1];
})
.attr("opacity", .6)
.style("fill", function(d) {
//Get data value
var value = d.average_revisit;
if (value) {
//If value exists…
return color(value);
} else {
//If value is undefined…
return "#ccc";
}
})
//Define position of each rectangle by it's latitude from the data
.attr("transform", function(d) {
return "translate(" + projection([-180, d.latitude]) + ")"
})
.attr("d", path);
});
});
function zoomed() {
projection
.translate(zoom.translate())
.scale(zoom.scale());
svg.selectAll("*")
.attr("d", path);
}
d3.select(self.frameElement).style("height", height + "px");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="https://gist.github.com/sadbumblebee/64581d15b89b361a17a85e8f37172673.js"></script>
<script src="https://gist.github.com/sadbumblebee/359fc826e4e55c8c3dcb3d279783e910.js"></script>
One idea to get the appropriate width would be to basis it on the size of your land path:
var land;
d3.json("world-110m.json", function(error, world) {
if (error) throw error;
land = g.insert("path", ".graticule")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
....
function getLandWidth() {
return land ? land.node().getBBox().width : 0;
}
...
function zoomed() {
projection
.translate(zoom.translate())
.scale(zoom.scale());
svg.selectAll("path")
.attr("d", path);
g.selectAll(".bars")
.attr("height", function(d) {
return projection([0, d.latitude - 0.5])[1] - projection([0, d.latitude])[1];
})
.attr("transform", function(d) {
return "translate(" + projection([-180, d.latitude]) + ")"
})
.attr("width", getLandWidth());
}
Full code here.

D3 - Load in and display data, row by row with set delay

I am trying to use time series data to plot bubbles on a map. What I would like to do is slowly plot these bubbles based on their date rather than all at once.
Something similar to:
http://www.nytimes.com/interactive/2014/upshot/mapping-the-spread-of-drought-across-the-us.html?_r=0
Here is some sample data:
date count code,country,lat,lon,counter
1/28/16 3 AND,Andorra,42.5,1.516667,0.577121255
1/29/16,146,ARE,United Arab Emirates,24.46666667,54.366667,2.264352856
1/30/16,13,AFG,Afghanistan,34.51666667,69.183333,1.213943352
Example of D3 Map
I have already looked at MB's tutorials on Path Transitions, Udacity's course on D3, and many questions on Stack Overflow.
I have previously tried using setInterval and setTimeout but most of the examples were with multiple data files. I would like to use one datafile line by line.
Code:
var width = 960,
height = 500;
var projection = d3.geo.mercator()
.center([0, 5 ])
.scale(200)
.rotate([-180,0]);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([0, 0])
.html(function(d) {
return "<strong>Frequency:</strong> <span style='color:white'>" + d.count + "</span>"+ "<br/>" + d.country;
})
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var path = d3.geo.path()
.projection(projection);
var g = svg.append("g");
// load and display the World
d3.json("world-110m2.json", function(error, topology) {
svg.call(tip)
// load and display the cities
d3.csv("cities2_or.csv", function(error, data) {
max = d3.max(data, function(d)
{return +d.counter})
coloring = d3.scale.linear()
.domain([0, max])
.range(["blue", "green"])
radiusing = d3.scale.linear()
.domain([0, max])
//.domain([0, 100])
.range([2, 30])
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];
})
.style("r", function(d){
return radiusing(+d.counter)
;})
//.style("opacity", .5)
.style("fill", function(d){
return coloring(+d.counter);
})
});
Thanks for any help
I took #adilapapaya suggestions and tried using the delay function but I am only able to plot the first point.
Instead of the g.selectAll that I was using above I have replaced it with the following. This however only plots the first point in my csv file and then stops.
g.append("circle")
.data(data)
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];
})
.style("r", 20)
.transition()
.duration(1)
.delay(function(d, i) { return i*1; })
.style("r",30)
.style("fill","green");
Thanks to Andrew Guy who helped find the JS fiddle example for me. Worked perfectly. My issue was not using the enter before hand. Here is the final code. Please respond if anyone has any questions.
var width = 960,
height = 500;
var projection = d3.geo.mercator()
.center([0, 5 ])
.scale(200)
.rotate([-180,0]);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([0, 0])
.html(function(d) {
return "<strong>Frequency:</strong> <span style='color:white'>" + d.count + "</span>"+ "<br/>" + d.country;
})
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var path = d3.geo.path()
.projection(projection);
var g = svg.append("g");
// load and display the World
d3.json("world-110m2.json", function(error, topology) {
svg.call(tip)
// load and display the cities
d3.csv("cities2_or.csv", function(error, data) {
max = d3.max(data, function(d)
{return +d.counter})
coloring = d3.scale.linear()
.domain([0, max])
.range(["blue", "green"])
radiusing = d3.scale.linear()
.domain([0, max])
//.domain([0, 100])
.range([2, 30])
g.selectAll("circle")
.data(data)
.enter()
.append("circle")
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.attr("cx", function(d) {
return projection([d.lon, d.lat])[0];
})
.attr("cy", function(d) {
return projection([d.lon, d.lat])[1];
})
// .style("r", function(d){
// return radiusing(+d.counter)
// ;})
//.style("opacity", .5)
.style("fill", function(d){
return coloring(+d.counter);})
.style("r",0)
.transition()
.duration(500)
.delay(function(d, i) { return i*200; })
.style("r",30);
});
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d", path)
});
// zoom and pan
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("circle")
.attr("d", path.projection(projection));
g.selectAll("path")
.attr("d", path.projection(projection));
});
svg.call(zoom)

Mapping an arc in D3 using d3.geo.path()

I'm trying to plot an arc connecting two points on a map of the USA.
The code I have used to make the map of the usa is
var path = d3.geo.path()
.projection(projection);
var graticule = d3.geo.graticule()
.extent([[-98 - 45, 38 - 45], [-98 + 45, 38 + 45]])
.step([5, 5]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
queue()
.defer(d3.json,'us.json')
.await(makeMyMap);
function makeMyMap(error, us) {
if (error) throw error;
svg.insert("path", ".graticule")
.datum(topojson.feature(us, us.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(us, us.objects.counties, function(a, b) { return a !== b && !(a.id / 1000 ^ b.id / 1000); }))
.attr("class", "county-boundary")
.attr("d", path);
svg.insert("path", ".graticule")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("class", "state-boundary")
.attr("d", path);
drawPath()
}
function drawPath() {
var route = svg.insert("path", ".graticule")
.datum({type: "LineString", coordinates: [[33,-118], [38.6,-78]]})
.attr("class", "route")
.attr("d", path);
}
Currently the path that is drawn by the drawPath() function is being made somewhere, but I can't view it on the map. If I don't set fill: none in the CSS then the screen will be blacked out, but setting it to a colour just makes the canvas be covered by that color.
The us.json file is used to make the map and is a topojson object.
You screwed up the positions for your LineString. According to the spec positions are specified as [longitude,latitude]. Since values for latitude cannot exceed 90 degrees, it's obvious that you need to switch the order of your coordinate values:
.datum({type: "LineString", coordinates: [[-118,33], [-78,38.6]]})
Thanks to the comment by Mark who took the time and made the effort this is also available in his working demo.

Zooming datasets in d3.js

I have overlayed two datasets, a boundary map and a point map in d3.js. I want to be able to zoom both datasets at the same time. With the current code, only the point map responds to the zoom. How can I zoom both datasets at the same time
The code is shown below
var canvas = d3.select("body").append("svg")
.attr("width",260)
.attr("height",400)
d3.json("/Maps/iowastate.json",function (data){
var group = canvas.selectAll("g")
.data(data.features)
.enter()
.append("g")
var projection =d3.geo.mercator()
.scale(250)
//.translate([0,0]);
var path = d3.geo.path().projection(projection);
var areas = group.append("path")
.attr("d", path)
.attr("class","area")
.attr("fill","black");
d3.csv("/Maps/detectors.csv",function (d){
var group = canvas.selectAll("g")
.data(d)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.StartLong,d.StartLat])[0];
})
.attr("cy", function(d,i) {
return projection([d.StartLong,d.StartLat])[1];
})
.attr("r", 0.1)
.style("fill", "red");
//console.log(projection(d[0].StartLat))
var zoom = d3.behavior.zoom()
.on("zoom",function(){
group.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
group.selectAll("path")
.attr("d", path.projection(projection));
});
canvas.call(zoom)
})
var zoom = d3.behavior.zoom()
.on("zoom",function(){
group.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
group.selectAll("path")
.attr("d", path.projection(projection));
});
canvas.call(zoom)
})
You are applying the right modifications, but twice to the same set of elements instead of the two different layers. To make it work, keep a reference to the other group (e.g. by using different variable names) and apply the transformations to both groups.

Categories