I'm trying to make a map using d3.js and three data files:
a simple .topojson file with my base map
a .geojson file with polygonal areas
a .csv file with data (which has a field in common with the .geojson)
First, I created my base map with this code:
var width = 360,
height = 600,
width2 = 479;
var proj = d3.geo.mercator()
.center([7.76, 48.57])
.scale(130000)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(proj);
var svg = d3.select("#carte").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("strasbourg.json", function(error, base) {
svg.append("path")
.data(topojson.feature(base, base.objects.contour).features)
.attr("class", "fond-stras")
.attr("d", path);
The result was fine (http://raphi.m0le.net/data/BAS/App_d3_strasrk/etape%201.html), so I made my code more complex:
var width = 360,
height = 600,
width2 = 479;
var proj = d3.geo.mercator()
.center([7.76, 48.57])
.scale(130000)
.translate([width / 2, height / 2]);
// This array will be used to the next choropleth
var couleurs = ['rgb(165,15,21)','rgb(222,45,38)','rgb(251,106,74)',
'rgb(252,146,114)','rgb(252,187,161)','rgb(254,229,217)'];
var path = d3.geo.path()
.projection(proj);
var svg = d3.select("#carte").append("svg")
.attr("width", width)
.attr("height", height);
// I use queue.v1.min.js to load my .topojson, .geojson and .csv
queue()
.defer(d3.json,"strasbourg.json")
.defer(d3.json, "chute_ries.json")
.defer(d3.csv, "progression_riesk.csv")
.await(ready);
function ready(error,base,areas,results) {
// I declare my base map like before, it works always fine
svg.append("path")
.data(topojson.feature(base, base.objects.contour).features)
.attr("class", "fond-stras")
.attr("d", path);
// the problematic part begins here :
svg.append("g").attr("class","areas")
.selectAll("path")
.data(areas.features)
.enter()
.append("path")
.attr("class", "area")
.attr("d", path)
// I write this to test an automatic colouring
.attr("fill", function(d,i){
if (results[i].diff_ries<-23){
return couleurs[0]
}
else {return couleurs[4]
}
})
Unfortunately, it doesn't work as you will see here: http://raphi.m0le.net/data/BAS/App_d3_strasrk/etape%202.html
Despite my efforts, I have not been able to get this to work. My .geojson was created with QGis, so I guess that it respects the Right-Hand-Rule.
My console does not display any errors so I haven't been able to determine what is wrong. I suspect that it could be the data() declaration, but I've seen several examples where it was written like this with .geojson data and worked perfectly.
It was a little tricky, but the problem comes from the projection of my original file : Lambert-93 (a french reference) instead of the Mercator precised in the script !
I resaved the file with a Mercator projection, and all worked perkectly fine !
Related
I have a projection of the world-50m.json file working, however when I fill it with a color there are several countries that are cut off on the edges which create horizontal fill sections/lines across the entire map.
This is actually visible on the d3-geo example projection here: https://github.com/d3/d3-geo/blob/master/test/data/world-50m.json
Is there another JSON file without these cutoff countries? Or perhaps I could omit specific polygons from my fill? Not quite sure how I'd locate every country/shape with the issue. While most are tiny and wouldn't be missed if omitted, a major one appears to be Russia.
Here is my code for reference:
var w = 960,
h = 660,
active = d3.select(null);
var projection = d3.geoMercator()
.scale(150)
.translate([w/2, h/2])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var countries = svg.append("svg:g").attr("id", "countries");
d3.json("world-50m.json", function(error, us) {
mapFeatures = topojson.feature(us, us.objects.countries).features;
mapFeatures.type = "countries";
drawMap();
});
function drawMap() {
countries.selectAll("path")
.data(mapFeatures)
.enter().append("svg:path")
.attr("d", path)
.attr("class", "feature")
.attr("data-id", function(d) { return d.id; })
.style("fill", "blue")
.style("stroke", "white")
.style("stroke-width", ".2px");
};
Any help would be greatly appreciated!
This solution is detailed in the comments above and was achieved with the help of #Andrew Reid and #altocumulus.
The observed issue was an instance of Antimeridian Cutting that was not being properly handled by d3, due to a mismatch of path and projection calls between d3 v3 and d3 v4 syntax.
The issue was solved by changing geo.path() to geoPath() which corrected the mismatch and enabled d3 to render the map correctly with its antimeridian cutting support.
Below is the correct code:
var w = 960,
h = 660,
active = d3.select(null);
var projection = d3.geoMercator()
.scale(150)
.translate([w/2, h/2])
.precision(.1);
var path = d3.geoPath()
.projection(projection);
var countries = svg.append("svg:g").attr("id", "countries");
d3.json("world-50m.json", function(error, us) {
mapFeatures = topojson.feature(us, us.objects.countries).features;
mapFeatures.type = "countries";
drawMap();
});
function drawMap() {
countries.selectAll("path")
.data(mapFeatures)
.enter().append("svg:path")
.attr("d", path)
.attr("class", "feature")
.attr("data-id", function(d) { return d.id; })
.style("fill", "blue")
.style("stroke", "white")
.style("stroke-width", ".2px");
};
I am trying to draw the states of India map (with disputed territories ) by D3 Map. I successfully generate the topojson file which looks good in http://www.mapshaper.org/
And the json link is https://dl.dropboxusercontent.com/s/wk9qd47wn1nhsjm/dddtopo.json
But I failed to draw the map. The link http://jsfiddle.net/sEFjd/47/ is how I did under jsfiddle.
var topoJsonUrl = "https://dl.dropboxusercontent.com/s/wk9qd47wn1nhsjm/dddtopo.json";
var width = 360,
height = 360;
d3.json(topoJsonUrl, function(error, mx) {
var projection = d3.geo.mercator();
// create the path
var path = d3.geo.path().projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
console.log(mx);
var geoPaths = svg.append("g")
.attr("class", "municipalities")
.selectAll("path")
.data(topojson.feature(mx, mx.objects.india).features);
geoPaths.enter().append("path")
.attr("d", path);
var p= svg.append("path")
.datum(topojson.mesh(mx, mx.objects.india))
.attr("d", path)
.attr("class", "state-boundary");
geoPaths.style("fill", function(d) {
return Math.random() > 0.5 ?'gray' : 'blue';
});
});
The code works well with other countries(Germany, Mexico) Not sure why it does not work this time. Any help will be very appreciated.
I'm trying to replicate http://bl.ocks.org/mbostock/4060606 using a UK Counties map.
I followed the following steps - pretty much what is suggested on http://bost.ocks.org/mike/map:
1) I downloaded the shapefile from Ordnance Survey and extracted some data using qGIS
2) when ready, I translated the shapefile into GeoJSON using ogr2ogr
3) I converted the GeoJSON into topoJSON making sure the IDs were preserved
I pretty much copied the original example for the choropleth from mbostock. However, instead of a nice map, I get a... circle. I wonder if I'm doing some errors with the projection?
For completeness, this is the javascript part of the page:
var width = 960,
height = 600;
var rateById = d3.map();
var quantize = d3.scale.quantize()
.domain([0, .15])
.range(d3.range(9).map(function(i) { return "q" + i + "-9"; }));
var projection = d3.geo.albers()
.center([0, 55.4])
.rotate([4.4, 0])
.parallels([50, 60])
.scale(50)
.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);
queue()
.defer(d3.json, "uk.topo.json")
.defer(d3.tsv, "unemployment.tsv", function(d) { rateById.set(d.id, +d.rate); })
.await(ready);
function ready(error, uk) {
svg.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(uk, uk.objects.counties).features)
.enter().append("path")
// .attr("class", function(d) { return quantize(rateById.get(d.id)); })
.attr("class", "q5-9" )
.attr("d", path);
// svg.append("path")
// .datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
// .attr("class", "states")
// .attr("d", path);
}
d3.select(self.frameElement).style("height", height + "px");
The counties topoJSON is too big to be pasted here, but roughly it's:
{"type":"Topology","objects":{"counties":{"type":"GeometryCollection","bbox":[220956.7,35190.3,655967.1,586683],"geometries":[{"type":"Polygon","properties":{"name":"Buckinghamshire"},"id":11901,"arcs":[[-1,-2,-3,-4,-5,-6]]},{"type":"Polygon","properties":{"name":"Cambridgeshire"},"id":1386,"arcs":[[-7,-8,-9,-10,-11,-12,-13,-14]]},{"type":"Polygon","properties":{"name":"Cumbria"},"id":13244,"arcs":[[-15,-16,-17]]},{"type":"Polygon","properties":{"name":"Derbs"},"id":13688,"arcs":[[-18,-19,-20,-21],[-22]]},...},"arcs":[[[5876,2688],[-67,53],[-21,101],[7,65],[96,66],[-7,66],[-78,69],[-234,12],[-5,42],...},"transform":{"scale":[43.5053905390539,55.15478547854785],"translate":[220956.7,35190.3]}}
I'm not a great expert here so I might be doing something fundamentally wrong. However, I have one certainty:
the UK counties map is correct, as it displays correctly on http://www.mapshaper.org/
Any idea? I'm happy to paste the complete files if needed.
Thanks!
The coordinates seem to be already projected (i.e. cartesian coordinates).
In this case you should use
d3.geo.path().projection(null);
But make sure you scale your topojson first to the desired size
topojson --width=960 --height=500 --margin=10 --cartesian -o out.json -- in.shp
Or reproject the shapefile first using ogr2ogr
ogr2ogr -t_srs EPSG:4326 out.shp in.shp
I'm a bit of a newb with D3 and I'm trying to work on a project for the organization I work for. I need to draw a choropleth map of Kenya with some data we collected. I'm working off Scott Murray's book Interactive Data Visualization for the Web. In his book he uses the following to generate paths from a json file of US States:
//Width and height
var w = 500;
var h = 300;
//Define default path generator
var path = d3.geo.path();
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Load in GeoJSON data
d3.json("us-states.json", function(json) {
//Bind data and create one path per GeoJSON feature
svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path);
});
I tried adapting this code to draw Kenyan counties from a json file I created from the Kenya shapefile I downloaded. The structure of the json file looks just like that of the US states file but when I look at the HTML in a browser I don't see any lines. I check the console and the path placeholders are there there is no data. If I swap in the US-states.json file I see the paths with data and the map in the browser.
Can someone help me please.
Thanks
I am doing something similar for Nairobi. As Lars has said in the comments it looks like you haven't set a projection for the map. The code below uses a mercator projection and the map is centered on Nairobi.
(Note that the scale is very zoomed and you would have to decrease this to get the whole of Kenya in).
var margin = {top: 50, right: 20, bottom: 20, left: 60},
dynwidth = $("#nairobistock").width(),
rowheight = 460;
width = dynwidth - margin.left - margin.right,
height = rowheight - margin.top - margin.bottom;
var projection = d3.geo.mercator()
.center([36.8, -1.3])
.scale([90000])
.translate([width/2, height/2]);
var nairobipathing = d3.geo.path().projection(projection);
var svg = d3.select("#nairobistock").append("svg")
.attr("width", (width + margin.left + margin.right) )
.attr("height", (height + margin.top + margin.bottom) );
d3.json("topojson/KEN-3topo.json", function(error, nairobi){
if (error) return console.error(error);
console.log(nairobi);
console.log("topojson added");
svg.selectAll("path")
.data(topojson.feature(nairobi, nairobi.objects.suburbs).features)
.enter()
.append("path")
.attr("class", function(d) {return d.ID;})
.attr("d", nairobipathing );
});
Hope this helps.
I'm trying to plot a circle on a map using d3 and topojson, but I can't figure it out. Here is my code
var width = 960,
height = 500;
var projection = d3.geo.mercator()
.center([0, 5 ])
.scale(900)
.rotate([-180,0]);
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "svg-map");
var path = d3.geo.path()
.projection(projection);
var g = svg.append("g");
d3.json("/static/lib/topojson/world-110.json", function(error, topology) {
svg.append("path")
.datum(topojson.feature(topology, topology.objects.countries))
.attr("d", d3.geo.path().projection(d3.geo.mercator()));
var coordinates = projection([10,20])
svg.append('svg:circle')
.attr('cx', coordinates[0])
.attr('cy', coordinates[1])
.attr('r', 5);
});
The issue you're seeing (or not seeing) is because you have defined a projection in this line:
var projection = d3.geo.mercator()
.center([0, 5 ])
.scale(900)
.rotate([-180,0]);
Which you use to transform your coordinates. Then, the projections for paths of the topojson data are set with this:
.attr("d", d3.geo.path().projection(d3.geo.mercator()));
So, the topojson renders using a mercator projection with no transforms where as your circle renders using a mercator projection that has been transformed.
I'm not entirely sure how you want you're final map to look, but assuming that it's a world map with a dot in Africa just comment out the centre, scale and rotate lines in the projection definition as in:
var projection = d3.geo.mercator();
//.center([0, 5 ])
//.scale(900)
//.rotate([-180,0]);
And replace the d3.geo.path in the svg.append block with path as in:
.attr("d", d3.geo.path().projection(d3.geo.mercator()));
to
.attr("d", path);
Oh, and if you haven't you'll need to give the circle some fill, so something like
.style("fill", "red")