I am using a tutorial to learn how to generate maps in D3.v3, but I am using D3.v4. I am just trying to get some circles to appear on the map (see below). The code works except that the circles are over Nevada and should be in the Bay Area. I imagine this is a mismatch between projections of the map and the projected coordinates. I am not sure what projection the map is in, but I have tried to force it to be albersUsa (see commented out commands where I generate path) but this causes the entire map to disappear. Any help would be appreciated!
<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script>
var w = 960,
h = 600;
var projection = d3.geoAlbersUsa();
var path = d3.geoPath()
//.projection(projection)
d3.json("https://d3js.org/us-10m.v1.json", function(error, us) {
if (error) throw error;
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("class", "states")
.attr("d", path);
svg.append("path")
.attr("class", "state-borders")
.attr("d", path(topojson.mesh(us, us.objects.states)))
svg.append("path")
.attr("class", "county-borders")
.attr("d", path(topojson.mesh(us, us.objects.counties)));
aa = [-122.490402, 37.786453];
bb = [-122.389809, 37.72728];
svg.selectAll("circle")
.data([aa,bb]).enter()
.append("circle")
.attr("cx", function (d) { return projection(d)[0]; })
.attr("cy", function (d) { return projection(d)[1]; })
.attr("r", "8px")
.attr("fill", "red")
});
</script>
Your US json is already projected, and to show it you use a null projection:
var path = d3.geoPath()
//.projection(projection)
Without defining a projection, your topojson/geojson coordinates will be translated to straight pixel coordinates. It just so happens that this particular topojson file has pixel coordinates that are within [0,0] and [960,600], almost the same size as a default bl.ock view. Without knowing the projection used too create that file you cannot replicated that projection to align geographic features to your data. Unless you place your features with pixel values directly and skip the projection altogether (not useful for points not near identifiable landmarks or where precision matters).
Your US topojson features disappear when projecting with a geoUsaAlbers() because you are taking pixel coordinates on a plane and transforming them to svg coordinates as though they were points on a three dimensional globe (d3 projections expect latitude longitude pairs).
Instead, use a topojson or geojson that is unprojected. That is to say it contains latitude/longitude pairs and project that data along with your points. See this bl.ock for an example with unprojected (lat/long pairs) json for the US using your code (but assigning a projection to path).
To check if you have latitude/longitude pairs you can view the geometry of these features in a geojson file easily and see if the values are valid long, lat points. For topojson, the topojson library converts features to geojson, so you can view the geometries after this conversion.
Here's an unprojected topojson of the US: https://bl.ocks.org/mbostock/raw/4090846/us.json
Let's say you really wanted to use the same topojson file though, well we can probably deduce the projection it uses. First, I'll show the difference between your projected points (by using an unprojected outline of the US) and the already projected topojson (the unprojected topojson is projected with d3.geoAlbersUsa() and the projected with a null projection):
Chances are the projection d3.geoAlbersUsa is optimized for a bl.ocks.org default viewport, 960x500. The unprojected dataset has a bounding box of roughly 960x600, so perhaps if we increase the scale by a factor of 600/500 and adjust the translate we can align our features in an svg that is 960x600:
var projection = d3.geoAlbersUsa();
var scale = projection.scale() * 600 / 500;
projection.scale(scale).translate([960/2,600/2])
var projectedPath = d3.geoPath().projection(projection);
And, this appears to align fairly well, I can't see the difference between the two:
Here's a block showing the aligned features.
But as I mention in the comments, even if you can align the features:
any zoom or centering would be problematic as you need to use a geoTransform on already projected data but a geoProjection on the raw geographic data. Using all (uniformly) projected data or all unprojected data makes life simpler.
Related
I am trying to visualize russians regions. I got data from here, validate here and all was well - picture.
But when I try to draw it, I receive only one big black rectangle.
var width = 700, height = 400;
var svg = d3.select(".graph").append("svg")
.attr("viewBox", "0 0 " + (width) + " " + (height))
.style("max-width", "700px")
.style("margin", "10px auto");
d3.json("83.json", function (error, mapData) {
var features = mapData.features;
var path = d3.geoPath().projection(d3.geoMercator());
svg.append("g")
.attr("class", "region")
.selectAll("path")
.data(features)
.enter()
.append("path")
.attr("d", path)
});
Example - http://ustnv.ru/d3/index.html
Geojson file - http://ustnv.ru/d3/83.json
The issue is the winding order of the coordinates (see this block). Most tools/utilities/libraries/validators don't really care about winding order because they treat geoJSON as containing Cartesian coordinates. Not so with D3 - D3 uses ellipsoidal math - benefits of this is include being able to cross the antimeridian easily and being able to select an inverted polygon.
The consequence of using ellipsoidal coordinates is the wrong winding order will create a feature of everything on the planet that is not your target (inverted polygon). Your polygons actually contain a combination of both winding orders. You can see this by inspecting the svg paths:
Here one path appears to be accurately drawn, while another path on top of it covers the entire planet - except for the portion it is supposed to (the space it is supposed to occupy covered by other paths that cover the whole world).
This can be simple to fix - you just need to reorder the coordinates - but as you have features that contain both windings in the same collection, it'll be easier to use a library such as turf.js to create a new array of properly wound features:
var fixed = features.map(function(feature) {
return turf.rewind(feature,{reverse:true});
})
Note the reverse winding order - through an odd quirk, D3, which is probably the most widespread platform where winding order matters actually doesn't follow the geoJSON spec (RFC 7946) on winding order, it uses the opposite winding order, see this comment by Mike Bostock:
I’m disappointed that RFC 7946 standardizes the opposite winding order
to D3, Shapefiles and PostGIS. And I don’t see an easy way for D3 to
change its behavior, since it would break all existing (spherical)
GeoJSON used by D3. (source)
By rewinding each polygon we get a slightly more useful map:
An improvement, but the features are a bit small with these projection settings.
By adding a fitSize method to scale and translate we get a much better looking map (see block here):
Here's a quick fix to your problem, projection needs a little tuning, also path has fill:#000 by default and stroke: #FFF could make it more legible.
var width = 700, height = 400;
var svg = d3.select(".graph").append("svg")
.attr("viewBox", "0 0 " + (width) + " " + (height))
.style("max-width", "700px")
.style("margin", "10px auto");
d3.json("mercator_files/83.json", function (error, mapData) {
var features = mapData.features;
var center = d3.geoCentroid(mapData);
//arbitrary
var scale = 7000;
var offset = [width/2, height/2];
var projection = d3.geoMercator().scale(scale).center(center)
.translate(offset);
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("class", "region")
.selectAll("path")
.data(features)
.enter()
.append("path")
.attr("d", path)
});
I am trying to visualize russians regions. I got data from here, validate here and all was well - picture.
But when I try to draw it, I receive only one big black rectangle.
var width = 700, height = 400;
var svg = d3.select(".graph").append("svg")
.attr("viewBox", "0 0 " + (width) + " " + (height))
.style("max-width", "700px")
.style("margin", "10px auto");
d3.json("83.json", function (error, mapData) {
var features = mapData.features;
var path = d3.geoPath().projection(d3.geoMercator());
svg.append("g")
.attr("class", "region")
.selectAll("path")
.data(features)
.enter()
.append("path")
.attr("d", path)
});
Example - http://ustnv.ru/d3/index.html
Geojson file - http://ustnv.ru/d3/83.json
The issue is the winding order of the coordinates (see this block). Most tools/utilities/libraries/validators don't really care about winding order because they treat geoJSON as containing Cartesian coordinates. Not so with D3 - D3 uses ellipsoidal math - benefits of this is include being able to cross the antimeridian easily and being able to select an inverted polygon.
The consequence of using ellipsoidal coordinates is the wrong winding order will create a feature of everything on the planet that is not your target (inverted polygon). Your polygons actually contain a combination of both winding orders. You can see this by inspecting the svg paths:
Here one path appears to be accurately drawn, while another path on top of it covers the entire planet - except for the portion it is supposed to (the space it is supposed to occupy covered by other paths that cover the whole world).
This can be simple to fix - you just need to reorder the coordinates - but as you have features that contain both windings in the same collection, it'll be easier to use a library such as turf.js to create a new array of properly wound features:
var fixed = features.map(function(feature) {
return turf.rewind(feature,{reverse:true});
})
Note the reverse winding order - through an odd quirk, D3, which is probably the most widespread platform where winding order matters actually doesn't follow the geoJSON spec (RFC 7946) on winding order, it uses the opposite winding order, see this comment by Mike Bostock:
I’m disappointed that RFC 7946 standardizes the opposite winding order
to D3, Shapefiles and PostGIS. And I don’t see an easy way for D3 to
change its behavior, since it would break all existing (spherical)
GeoJSON used by D3. (source)
By rewinding each polygon we get a slightly more useful map:
An improvement, but the features are a bit small with these projection settings.
By adding a fitSize method to scale and translate we get a much better looking map (see block here):
Here's a quick fix to your problem, projection needs a little tuning, also path has fill:#000 by default and stroke: #FFF could make it more legible.
var width = 700, height = 400;
var svg = d3.select(".graph").append("svg")
.attr("viewBox", "0 0 " + (width) + " " + (height))
.style("max-width", "700px")
.style("margin", "10px auto");
d3.json("mercator_files/83.json", function (error, mapData) {
var features = mapData.features;
var center = d3.geoCentroid(mapData);
//arbitrary
var scale = 7000;
var offset = [width/2, height/2];
var projection = d3.geoMercator().scale(scale).center(center)
.translate(offset);
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("class", "region")
.selectAll("path")
.data(features)
.enter()
.append("path")
.attr("d", path)
});
I've made effective D3 maps using rasters (d3.tile and map libraries) and vectors (TopoJSON in SVG shapes). But I hit a bug when I combine them.
I followed Mike Bostock's raster-and-vector examples, especially his "Raster & Vector III", which changes the transform and stroke width to update how the vectors are displayed.
My map almost works perfectly. However, upon loading, only the raster tiles are displayed; the vectors are invisible:
But as soon as I trigger the d3.zoom event (by panning or zooming), the vectors are displayed:
I don't understand this, because I explicitly tell the browser, independently of the zoom event, to draw the vectors. This is the relevant snippet:
// read in the topojson
d3.json("ausElectorates.json", function(error, mapData) {
if (error) throw error;
var electorates = topojson.feature(mapData, mapData.objects.tracts);
// apply a zoom transform equivalent to projection{scale,translate,center}
map.call(zoom)
.call(zoom.transform, d3.zoomIdentity
.translate(mapWidth / 2, mapHeight / 2)
.scale(1 << 12)
.translate(-centre[0], -centre[1]));
// draw the electorate vectors
vector.selectAll("path")
.data(electorates.features)
.enter().append("path")
.attr("class", "electorate")
.attr("d", path);
});
For some reason, that last line of the d3.json() function -- .attr("d", path") -- isn't visualising the vectors.
Click here to see the map. Click here to access the full code and the TopoJSON it uses.
I'd love advice on this one, which is baffling me!
(PS Apologies for omitting copyright attributions for the map tiles, D3.js library, etc - I'm just trying to minimise the code for this example.)
It is drawing the vectors - however, you can't rely on solely scaling and translating your vector with the d3 geoProjection as when you zoom you apply the translate and scale to the path itself - not the projection:
vector.selectAll("path")
.attr("transform", "translate(" + [change.x, change.y] + ")scale(" + change.k + ")")
.style("stroke-width", 1 / change.k);
Since you don't set scale and translate, when loading your vectors they just aren't drawn correctly. They are drawn very small - as your projection scale is 1/tau, with a translation of [0,0]. Inspecting the svg on page load shows that they are there, and they are tiny.
The solution is to draw your vectors prior to map.call("zoom") - this way you can apply the base transform (center, transform, and scale) on the path before manually zooming:
// read in the topojson
d3.json("ausElectorates.json", function(error, mapData) {
if (error) throw error;
var electorates = topojson.feature(mapData, mapData.objects.tracts);
// draw the electorate vectors
vector.selectAll("path")
.data(electorates.features)
.enter().append("path")
.attr("class", "electorate")
.attr("d", path);
// apply a zoom transform equivalent to projection{scale,translate,center}
map.call(zoom)
.call(zoom.transform, d3.zoomIdentity
.translate(mapWidth / 2, mapHeight / 2)
.scale(1 << 12)
.translate(-centre[0], -centre[1]));
});
I'm new to d3.js. I'm trying to create this choropleth map from scratch:
My script is showing no errors and in elements tab I can see data but nothing is showing on screen. I have read multiple documentations but still don't know what I'm missing.
jsfiddle:
https://jsfiddle.net/jx3hjfgw
var width = 960,
height = 1160;
// SVG element as a JavaScript object that we can manipulate later
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
var center = [24.679341, 46.680381];
// Instantiate the projection object
var projection = d3.geo.conicConformal()
.center(center)
.clipAngle(180)
// Size of the map itself, you may want to play around with this in
// relation to your canvas size
.scale(10000)
// Center the map in the middle of the canvas
.translate([width / 2, height / 2])
.precision(.1);
// Assign the projection to a path
var path = d3.geo.path().projection(projection);
d3.json("https://code.highcharts.com/mapdata/countries/sa/sa-all.geo.json", function(err, data) {
$.each(data.features, function(i, feature) {
svg.append("path")
.datum(feature.geometry)
.attr("class", "border")
.attr("stroke", "black")
.attr("fill", "blue");
});
});
There are a couple issues with your map.
The primary issue is that your projection is not WGS84, that is to say it does not comprise of longitude/latitude pairs. The projection of the geojson is specified in your geojson itself:
"crs":{"type":"name","properties":{"name":"urn:ogc:def:crs:EPSG:32638"}}
CRS stands for spatial reference system
This projection (EPSG:32638) is a UTM system. D3 uses unprojected data (or data that is 'projected' to WGS84), points on a three dimensional ellipsoid, not already projected points on a planar grid (like UTM). If the geojson did not indicate what projection it used, you can still tell that it is not WGS84, or longitude latitude pairs, because the coordinates are not valid longitude/latitudes:
...[1348,4717],[1501,4754],[1572,4753]...
You have two options, one is to use unprojected data (or data in WGS84) and build your map around a d3 projection. The other is to use a geo.transform to show your data.
Solution 1
As noted in the other answer, you'll need to use the path function to actually display the data, you also shouldn't need an each loop in d3 to append features
For this solution to work, and it is probably the most straight-forward, you'll need to either re-project/un-project the geojson you have (with some tool other than d3) or, alternatively, you'll have to find an new data source (which is probably not too difficult) with the spatial data represented as long/lat pairs.
Also note that coordinates in geojson and d3 are [long,lat] therefore your centering coordinate of [24.679341, 46.680381] refers to 46.7 degrees North, 24.7 degrees East - this point is not in Saudi Arabia but is in Romania.
Take a look at an example projection suitable for Saudi Arabia here (using a basic geojson - just a country outline) (in d3v4 - slight differences from v3).
Together, that would give you something like:
var projection = d3.geo.mercator()
.center([46.680381,24.679341])
.scale(1000)
.translate([width / 2, height / 2])
.precision(.1);
var path = d3.geo.path().projection(projection)
d3.json("source.json", function(err, data) {
svg.selectAll("path")
.data(data.features)
.enter()
.append('path')
.attr("class", "border")
.attr("stroke", "black")
.attr("fill", "blue")
.attr("d", path);
});
Note that if you really want to keep the conical projection (d3.geo.conicConformal()), take a look on centering that type of projection here, it's for an albers projection, but the method is the same as they are both conical projections of the same sort.
Solution 2
The other option is to use a geo.transfrom which will translate and scale your data (since it is already planar in this case) to match your desired view. The downside of this approach is that you can't use a lat/long point to indicate anything - the map units will be the projection units and the map units are not degrees longitude or latitude.
The goal is to translate/scale/shift coordinates such as these:
...[1348,4717],[1501,4754],[1572,4753]...
to [x,y] pixel values that are within your SVG bounds.
This is more complex, you can try to figure out the translate manually (like I demonstrate with your data in this bl.ock, (uses d3v4, which has slightly different method names, but otherwise is the same for this type of operation), or use an automated function to determine the appropriate transform you need to use.
Change your last function to this
svg.append("path")
.data(feature.geometry)
.attr("d", path)
.attr("class", "border")
.attr("stroke", "black")
.attr("fill", "blue");
I think there is something wrong with you geojson file
I have uploaded it to github, which is automatically detecting geojson content and displays it as a map. You can see that only one line is displayed
As already pointed out, you don't have d property defined for path
I have updated your fiddle to match Github result:
var width = 960,
height = 1160;
// SVG element as a JavaScript object that we can manipulate later
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
var center = [24.679341, 46.680381];
// Instantiate the projection object
var projection = d3.geo.mercator()
.center(center)
.clipAngle(180)
// Size of the map itself, you may want to play around with this in
// relation to your canvas size
.scale(1000)
// Center the map in the middle of the canvas
.translate([width / 2, height / 2])
.precision(.1);
// Assign the projection to a path
var path = d3.geo.path().projection(projection);
d3.json("https://raw.githubusercontent.com/bumbeishvili/Assets/master/Other/junk/sa-all.geo.json", function(err, data) {
debugger;
svg.selectAll('path')
.data(data.features)
.enter()
.append("path")
.attr("d", path)
.attr("class", "border")
.attr("stroke", "black")
.attr("fill", "blue");
});
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<div id="map"></div>
I am trying to make this map of the us scale smaller. Either to my SVG, or even manually.
This is my code in its simplest from:
function initializeMapDifferent(){
var svg = d3.select("#map").append("svg")
.attr("width", 1000)
.attr("height", 500);
d3.json("https://d3js.org/us-10m.v1.json", function (error, us){
svg.append("g")
.attr("class", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("fill", "gray")
.attr("d", d3.geoPath());
});
}
I have tried something like:
var path = d3.geoPath()
.projection(d3.geoConicConformal()
.parallels([33, 45])
.rotate([96, -39])
.fitSize([width, height], conus));
but every time I add anything to my path variable I get NAN errors from the internal parts of D3. Thanks for any help!
Why the data doesn't project properly
The key issue is that your data is already projected. D3 geoProjections use data that is unprojected, or in lat long pairs. Data in the WGS84 datum. Essentially a d3 geoProjection takes spherical coordinates and translates them into planar cartesian x,y coordinates.
Your data does not conform to this - it is already planar. You can see most evidently because Alaska is not where it should be (unless someone changed the lat long pairs of Alaska, which is unlikely). Other signs and symptoms of already projected data may be a feature that covers the entire planet, and NaN errors.
That this is a composite projection makes it hard to unproject, but you can display already projected data in d3.js.
"Projecting" already projected data
Null Projection:
Most simply, you can define your projection as null:
var path = d3.geoPath(null);
This will take the x,y data from the geojson geometries and display it as x,y data. However, if your x,y coordinates exceed the width and height of your svg, the map will not be contained within your svg (as you found in your example with .attr("d", d3.geoPath());).
The particular file in this question is pre-projected to fit a 960x600 map, so this is ideal for a null projection - it was designed with the dimensions in mind. Its units are pixels and all coordinates fall within the desired dimensions. However, most projected geometries use coordinate systems with units such as meters, so that the bounding box of the feature's coordinates may be millions of units across. In these cases the null projection won't work - it'll convert a map unit value to a pixel value with no scaling.
With d3, A null projection is commonly used with geojson/topojson that is preprojected to fit a specified viewport using a d3 projection. See command line cartography for an example (the example uses unprojected source files - the same issues that arise from using a d3 projection on projected data apply in both browser and command line). The primary advantage of preprojecting a file for use with a null projection is performance.
geoIdentity
If all you need is to scale and center the features, you can use a geoIdentity. This is implements a geoTransform but with standard projection methods such as scale, translate, and most importantly - fitSize/fitExtent. So, we can set the projection to a geoIdentity:
var projection = d3.geoIdentity();
This currently does the same as the null projection used above, it takes x,y data from the geojson geometries and displays it as x,y data with no transform - treating each coordinate in the geojson as a pixel coordinate. But, we can apply fitSize to this (or fitExtent) which will automatically scale and translate the data into the specified bounding box:
var projection = d3.geoIdentity()
.fitSize([width,height],geojsonObject);
or
var projection = d3.geoIdentity()
.fitExtent([[left,top],[right,bottom]], geojsonObject);
Keep in mind, most projected data uses geographic conventions, y=0 is at the bottom, with y values increasing as one moves north. In svg/canvas coordinate space, y=0 is at the top, with y values increasing as one moves down. So, we will often need to flip the y axis:
var projection = d3.geoIdentity()
.fitExtent([width,height],geojsonObject)
.reflectY(true);
This particular dataset: https://d3js.org/us-10m.v1.json was projected with a d3 projection, so its y axis has already been flipped as d3 projections project to a svg or canvas coordinate space.
geoIdentity Demo
var width = 600;
var height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("https://d3js.org/us-10m.v1.json", function (error, us){
var featureCollection = topojson.feature(us, us.objects.states);
var projection = d3.geoIdentity()
.fitExtent([[50,50],[600-50,300-50]], featureCollection)
var path = d3.geoPath().projection(projection)
svg.append("g")
.attr("class", "states")
.selectAll("path")
.data(featureCollection.features)
.enter().append("path")
.attr("fill", "gray")
.attr("d", path);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/2.2.0/topojson.js"></script>
geoTransform
If you want a little more control over how that data is displayed you can use a geoTransform.
From Mike Bostock:
But what if your geometry is already planar? That is, what if you just
want to take projected geometry, but still translate or scale it to
fit the viewport?
You can implement a custom geometry transform to gain complete control
over the projection process.
To use a geoTransform is relatively straightforward assuming that you do not want to change the type of projection. For example, if you want to scale the data you could implement a short function for scaling with geoTransform:
function scale (scaleFactor) {
return d3.geoTransform({
point: function(x, y) {
this.stream.point(x * scaleFactor, y * scaleFactor);
}
});
}
var path = d3.geoPath().projection(scale(0.2));
Though, this will scale everything into the top left corner as you zoom out. To keep things centered, you could add some code to center the projection:
function scale (scaleFactor,width,height) {
return d3.geoTransform({
point: function(x, y) {
this.stream.point( (x - width/2) * scaleFactor + width/2 , (y - height/2) * scaleFactor + height/2);
}
});
}
var path = d3.geoPath().projection(scale(0.2,width,height))
geoTransform Demo:
Here is an example using your file and a geoTransform:
var width = 600;
var height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
function scale (scaleFactor,width,height) {
return d3.geoTransform({
point: function(x, y) {
this.stream.point( (x - width/2) * scaleFactor + width/2 , (y - height/2) * scaleFactor + height/2);
}
});
}
d3.json("https://d3js.org/us-10m.v1.json", function (error, us){
var path = d3.geoPath().projection(scale(0.2,width,height))
svg.append("g")
.attr("class", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("fill", "gray")
.attr("d", path);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.6.0/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/2.2.0/topojson.js"></script>
Unproject the data
This method is useful under certain circumstances. But it requires you to know the projection that was used to create your data. Using QGIS/ArcGIS or even mapshaper you can change the data's projection so that it is "projected" as WGS84 (aka EPSG 4326). Once converted you have unprojected data.
In Mapshaper this is pretty easy with shapefiles, drag in the .dbf, .shp, and .prj files of a shapefile into the window. Open the console in mapshaper and type proj wgs84.
If you don't know the projection used to create the data, you can't unproject it - you don't know what transformation was applied and with what parameters.
Once unprojected, you can use regular d3 projections as normal as you have coordinates in the correct coordinate space: longitude latitude pairs.
Unprojecting is useful if you also have unprojected data and want to mix both in the same map. Alternatively you could project the unprojected data so that both use the same coordinate system. Combining unmatched coordinate systems in a map with d3 is not easy and d3 is likely not the correct vehicle for this. If you really want to replicate a specific projection with d3 to match features that are already projected with unprojected features, then this question may be useful.
How can you tell if your data is projected already?
You could do check to see that the geometry of your features respect the limits of latitude and longitude. For example, if you were to log:
d3.json("https://d3js.org/us-10m.v1.json", function (error, us){
console.log(topojson.feature(us, us.objects.states).features);
});
You will quickly see that values are in excess of +/- 90 degrees N/S and +/- 180 degrees E/W. Unlikely to be lat long pairs.
Alternatively, you could import your data to an online service such as mapshaper.org and compare against another topojson/geojson that you know is unprojected (or 'projected' using WGS84).
If dealing with geojson, you may be lucky enough to see a property that defines the projection, such as: "name": "urn:ogc:def:crs:OGC:1.3:CRS84" (CRS stands for coordinate reference system) or an EPSG number: EPSG:4326 (EPSG stands for European Petroleum Survey Group).
Also, if your data projects with a null projection but not a standard projection (scaled/zoomed out to ensure you aren't looking in the wrong area), you might be dealing with projected data. Likewise if your viewport is entirely covered by one feature (and you aren't zoomed in). NaN coordinates are also a potential indicator. However, these last indicators of projected data can also mean other problems.
Lastly, the data source may also indicate data is already projected either in meta data or how it is used: Looking at this block, we can see that no projection was used when the geoPath is defined.