I would like to minimize greenland and antartica on my D3 geojson map. How do I do this. I've tried scale and translate methods but they simply move the map around on the page not providing the minimized y coordinates.
image of map
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="d3.v3.js"></script>
<script src="topojson.min.js"></script>
<style>
</style>
<script type="text/javascript">
function draw(geo_data) {
"use strict";
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin)
.attr("height", height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geo.mercator();
var path = d3.geo.path().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgb(9, 157, 217)')
.style('stroke', 'black')
.style('stroke-width', 0.5);
};
</script>
</head>
<body>
<script type="text/javascript">
/*
Use D3 to load the GeoJSON file
*/
//d3.json("world-topo-min.json", draw);
d3.json("world_countries.json", draw);
</script>
</body>
</html>
If you want to remove the areas for Greenland and Antarctica on the fly, you can simply filter your GeoJSON FeatureCollection, i.e. geo_data.features. In this array you will find features for both Greenland ("id": "GRL") as well as Antarctica ("id": "ATA"). Hence, you can make use of the array's .filter() method to get rid of these two features leaving the rest untouched:
var map = svg.selectAll('path')
.data(geo_data.features.filter(d => d.id !== "GRL" && d.id !== "ATA))
I'm using the d3.geo.conicEquidistant() projection on a world map approximately centered on the Pacific, my aim is to draw circles from a point, illustrating a specific distance, say 1000km, from this point on the map.
How can i calculate the correct radius on the projection, given specific kilometers?
Here is my code example (yes, it's about missile reaches from North Korea. Using artificially Pyongyang as launching point :), building on this question: Scale a circle's radius (given in meters) to D3.js d3.geo.mercator map
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
stroke: white;
stroke-width: 0.25px;
fill: grey;
}
circle {
stroke: red;
stroke-width: 1px;
stroke-dasharray: 5;
fill: transparent;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<script>
var width = 960,
height = 500;
var projection = d3.geo.conicEquidistant()
.center([0, 5 ])
.scale((width + 1) / 2 / Math.PI)//.scale(150)
.rotate([-160,0]);
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");
var missiles = [
{
name: "missile1",
location: { // appx lat&long of pyongyang
latitude: 125.6720717,
longitude: 39.0292506
},
reach: 1000 //radius of circle in kilometers on map
},
{
name: "missile2",
location: { // appx lat&long of pyongyang
latitude: 125.6720717,
longitude: 39.0292506
},
reach: 3500 //radius of circle in kilometers on map
},
];
// load and display the World
d3.json("https://s3-us-west-2.amazonaws.com/vida-public/geo/world-topo-min.json", function(error, topology) {
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d", path)
});
svg.selectAll(".pin")
.data(missiles)
.enter().append("circle", ".pin")
.attr("r", scaledRadius)
/*function(d) {
return d.reach
})*/
.attr("transform", function(d) {
return "translate(" + projection([
d.location.latitude,
d.location.longitude
]) + ")"
})
</script>
</body>
</html>
I looked at this answer, trying it out above, but was not quite able to apply it to my code (i'm quite new to d3 to be honest, there might be something very obvious wrong):
https://stackoverflow.com/a/31616927/4623519
Further question: is this solution specific to the mercator projection?
(Let's ignore that we really need to hide Antarctica in this projection.)
There are two ways to achieve this.
One (the easier option) uses d3's geoCircle functionality to create a geographically circular feature:
var circle = d3.geoCircle().center([x,y]).radius(r);
For this, x and y are your center point in degrees, and r is the radius of the circle in degrees. To find the radius of a circle in meters, we need to convert meters into degrees - which is easiest if we assume a round earth (The earth is only slightly ellipsoid, so this is an induced error of up to 0.3%, but even if using an ellipsoid, the earth is really more potato shaped, so that too will induce error). Using a mean radius of 6,371 km we can get a rough formula like:
var circumference = 6371000 * Math.PI * 2;
var angle = distance in meters / circumference * 360;
var circle = d3.geoCircle().center([x,y]).radius(angle);
This gives us something like:
var width = 500;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var projection = d3.geoAlbers()
.scale(200)
.translate([width/2,height/2]);
var path = d3.geoPath().projection(projection);
var usa = {"type":"FeatureCollection", "features": [
{"type":"Feature","geometry":{"type":"MultiPolygon","coordinates":[[[[-94.81758,49.38905],[-88.378114,48.302918],[-82.550925,45.347517],[-82.439278,41.675105],[-71.50506,45.0082],[-69.237216,47.447781],[-66.96466,44.8097],[-70.11617,43.68405],[-70.64,41.475],[-73.982,40.628],[-75.72205,37.93705],[-75.72749,35.55074],[-81.49042,30.72999],[-80.056539,26.88],[-81.17213,25.20126],[-83.70959,29.93656],[-89.18049,30.31598],[-94.69,29.48],[-99.02,26.37],[-100.9576,29.38071],[-104.45697,29.57196],[-106.50759,31.75452],[-111.02361,31.33472],[-117.12776,32.53534],[-120.36778,34.44711],[-123.7272,38.95166],[-124.53284,42.76599],[-124.68721,48.184433],[-122.84,49],[-116.04818,49],[-107.05,49],[-100.65,49],[-94.81758,49.38905]]],[[[-155.06779,71.147776],[-140.985988,69.711998],[-140.99777,60.306397],[-148.018066,59.978329],[-157.72277,57.570001],[-166.121379,61.500019],[-164.562508,63.146378],[-168.11056,65.669997],[-161.908897,70.33333],[-155.06779,71.147776]]]]},"properties":{"name":"United States of America"},"id":"USA"}
]};
var circumference = 6371000 * Math.PI * 2;
var angle = 1000000 / circumference * 360;
var circle = d3.geoCircle().center([-100,40]).radius(angle);
svg.append("path")
.attr("d",path(usa));
svg.append("path")
.attr("d", path(circle()))
.attr("fill","steelblue");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
The other option is to create a geojson feature on the fly from custom functions rather than through d3. The easiest way may be to take a point, and calculate the points that are x meters away at bearings spaced 10 degrees apart (for 36 points to the circle). This requires calculating a point using start point, bearing and distance, the formula can be found here. A while ago I built an example tissot's indicatrix using this method: Tissot's Indicatrix Bl.ock.
Building on Andrew's answer, here is how i solved my problem:
Added styling, changed my code to d3 v4:
</!DOCTYPE html>
<html>
<head>
<title></title>
<style>
path {
stroke: white;
stroke-width: 0.25px;
fill: grey;
}
.circle {
stroke: red;
stroke-width: 1px;
stroke-dasharray: 5;
fill: transparent;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
The d3 script:
var width = 1200;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
Using the conic equidistant projection:
var projection = d3.geoConicEquidistant()
.center([0, 20])
.scale((width + 1) / 2 / Math.PI)
.rotate([-140,0]);
var path = d3.geoPath()
.projection(projection);
Creating circles from Andrew's answer, one with 1000km and other with 3500km radius. These could be arranged more nicely into lists/objects i guess.
var circumference = 6371000 * Math.PI * 2;
var angle1 = 1000000 / circumference * 360;
var missile1 = d3.geoCircle().center([125.6720717,39.0292506]).radius(angle1);
var angle2 = 3500000 / circumference * 360;
var missile2 = d3.geoCircle().center([125.6720717,39.0292506]).radius(angle2);
Loading and displaying the World:
var url = "http://enjalot.github.io/wwsd/data/world/world-110m.geojson";
//display the world
d3.json(url, function(err, geojson) {
svg.append("path")
.attr("d", path(geojson));
//append the circles
svg.append("path")
.attr("d", path(missile1()))
.attr("class","circle");
svg.append("path")
.attr("d", path(missile2()))
.attr("class","circle");
});
Edit: Added https to the json file and altered the code so the circles are drawn above the world map. I have a working jfiddle here: https://jsfiddle.net/y8qn8tr1/2/
I have rendered the world map using world-110m topojson. I directly used the code provided by Jakob on D3 mapping tutorial - http://www.digital-geography.com/d3-js-mapping-tutorial-1-set-initial-webmap/#.V4iGN9J97IV
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Set a style for our worldshape-data -->
<style>
path {
stroke: red;
stroke-width: 0.5px;
fill: grey;
}
</style>
<body>
<!-- implementation of the hosted D3- and TopoJson js-libraries -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<!-- map creation -->
<script>
// canvas resolution
var width = 1000,
height = 600;
// projection-settings for mercator
var projection = d3.geo.mercator()
// where to center the map in degrees
.center([0, 50 ])
// zoomlevel
.scale(100)
// map-rotation
.rotate([0,0]);
// defines "svg" as data type and "make canvas" command
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// defines "path" as return of geographic features
var path = d3.geo.path()
.projection(projection);
// group the svg layers
var g = svg.append("g");
// load data and display the map on the canvas with country geometries
d3.json("world-110m.json", function(error, topology) {
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d", path)
});
// zoom and pan functionality
/*var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("path")
.attr("d", path.projection(projection));
});
svg.call(zoom)*/
</script>
</body>
</html>
Now i want to reuse this code to render the map of India. But when I link it to India topoJSON file, only a blank SVG is created. The js console gives an error - topojson.v0.min.js:1 Uncaught TypeError: Cannot read property 'type' of undefined
I have placed world-110m.json and india.json on dropbox -
https://www.dropbox.com/sh/wrxyngyq4jdie9c/AACG2-dTzC79rRvlxlOC5poBa?dl=0
Its just a small error.
topojson.object(topology, topology.objects.countries //wrong
topojson.object(topology, topology.objects.collection // right
Next time check the data, by printing it to console and check its contents.
I am new to d3.js, but I could draw a topojson file, centering and calculate the scale (obviously using several questions and answers of stackoverflow). Now, I want to change the topojson displayed, I mean, delete the actual, and load a new one and display it.
I can load the new one, but it is not displayed. Could you help me with error?
<!DOCTYPE html>
<meta charset="utf-8">
<style>
rect {
fill: none;
pointer-events: all;
}
.feature {
fill: #ccc;
cursor: pointer;
}
.feature.active {
fill: orange;
}
.mesh {
fill: none;
stroke: #fff;
stroke-width: .5px;
stroke-linejoin: round;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, height])
.range([height, 0]);
var width = 960,
height = 500,
active;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.on("click", reset);
var g = svg.append("g");
var projection = d3.geo.albers()
.scale(1)
.translate([0,0]);
var path = d3.geo.path()
.projection(projection);
createMap("aguascalientes.topojson");
function createMap(topojsonFile) {
d3.json(topojsonFile, function(error, tj) {
console.log(tj);
for(key in tj.objects) { features = tj.objects[key]; }
var estados = topojson.feature(tj, features);
var b = path.bounds(estados);
var s = 0.95/ Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height);
var t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
projection
.scale(s)
.translate(t);
g.selectAll("path")
.data(topojson.feature(tj, features).features)
.enter().append("path")
.attr("d", path)
.attr("class", "feature")
.on("click", click);
g.append("path")
.datum(topojson.mesh(tj, features, function(a,b) { return a !== b; }))
.attr("class", "mesh")
.attr("d", path);
});
}
function updateData() {
console.log("Update");
svg = d3.select("body").transition();
createMap("estados.topojson");
}
</script>
<body>
<div id="option">
<input name="updateButton"
type="button"
value="Update"
onclick="updateData()"
/>
</div>
</body>
Thanks in advance
You aren't actually deleting the old one and operating only on the .enter() selection, hence nothing happens. To delete what's there before adding the new paths, do
svg.selectAll("path").remove();
after loading the data, i.e. just after the line
d3.json(topojsonFile, function(error, tj) {
I'm having an issue with a d3 zoomable map.
I'm loading the map from a previously built topojson file with a departmentsobject (the areas in the map) and a maternidadesobject (a few points in the map, initially rendered with crosses).
I'm using d3.behavior.zoom to implement the zoom behaviour, and I want it to be able to zoom using the mouse wheel and pan with drag. It works just fine with the map itself (the areas). However, the points in the map get shifted instantly to a wrong location at any zoom event. Also, the points' path is changed from crosses to circles somehow!
You can reproduce the issue and view the code here: http://bl.ocks.org/monsieurBelbo/5033491
Here is code:
<!DOCTYPE html>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.js"></script>
<script src="topojson.v0.min.js"></script>
<html>
<style>
.background {
fill: none;
pointer-events: all;
}
.department {
fill: #aaa;
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<body>
<script>
d3.json("santafe.json", function(error, theProvince) {
var width= 960, height= 500;
var svg = d3.select("body").append("svg");
var departments = topojson.object(theProvince, theProvince.objects.departments);
// The projection
var projection = d3.geo.mercator()
.scale(14000)
.center([-60.951,-31.2])
.translate([width / 2, height / 2]);
// The path
var path = d3.geo.path()
.projection(projection);
// Zoom behavior
var zoom = d3.behavior.zoom()
.translate(projection.translate())
.scaleExtent([height, Infinity])
.scale(projection.scale())
.on("zoom", function() {
projection.translate(d3.event.translate).scale(d3.event.scale)
map.selectAll("path.zoomable").attr("d", path);
});
// The map
var map = svg.append("g")
.classed("provinceMap", true)
.call(zoom);
map.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
// Departments
map.selectAll(".department")
.data(departments.geometries)
.enter().append("path")
.classed("department", true)
.classed("zoomable", true)
.attr("d", path);
// Places
map.selectAll(".place-label")
.data(topojson.object(theProvince, theProvince.objects.maternidades).geometries)
.enter().append("path")
.classed("place", true)
.classed("zoomable", true)
.attr("d", d3.svg.symbol().type("cross"))
.attr("transform", function(d) { return "translate(" + projection(d.coordinates.reverse()) + ")"; });
});
</script>
</body>
</html>
Any ideas?
Thanks!
UPDATE
Thanks to #enjalot 's suggestion, the issue was solved by re-translating the places on the zoom behaviour. Just add:
map.selectAll(".place").attr("transform", function(d) { return "translate(" + projection(d.coordinates) + ")"; });
to the zoom behavior. Check out a working version here: http://tributary.io/inlet/5095947