Using D3 to draw maps
First look at the renderings:
[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-ENi85nYQ-1678532273754)(null)]
First, the data geoJson used by D3 to draw maps can be obtained from the Alibaba Cloud data visualization platform:
[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-jyYrL5p3-1678532273772)(null)]
Then, among the commonly used projection methods for maps, I choose to use the Mercator projection, which is also commonly used by Baidu and Gaode maps.
We need to get a piece of data in GeoJson format from the data platform. The json data can be imported directly as an Object
object, or we can use d3.json
to get the remote Json data .This demonstration uses the data of a certain city as an example
-
Introducing D3
Native directly via
Or:
npm install d3
2. Create a container for map drawing
<body> <svg class="map" style="display: inline-block;"> </svg> </body>
-
The initialization of the SVG map container works:
const svgW = 1000 const svgH = 1000 const colors = ['white', 'blue', 'red']; const svg = d3. select(".map") svg.attr('viewBox', `0 0 1000 1000`) svg.attr("width", svgW).attr("height", svgH) .attr('xmlns', 'http://www.w3.org/2000/svg')
If you want to achieve responsive web design, set
viewBox
to get twice the result with half the effort -
Create G group and set projection parameters
const g = svg.append('g').attr("class", 'path-wrap').attr("width", svgW).attr("height", svgH) const projection = d3. geoMercator() // .fitSize([svgW, svgH], mapJson) // Use the latitude and longitude of the center of a city .center([116.418757, 39.917544]) //chain writing, .center([longitude, latitude]) sets the center of the map (real dimension) // .scale([18000]) //.scale([value]) set the map zoom // .translate([svgW / 2, svgH / 2]) //.translate([x,y]) sets the offset
One thing to note here is that the center needs to pass in the latitude and longitude parameters instead of the canvas parameters. We can download higher-level maps for this data, such as the map of Jiangsu Province, which will contain the center longitude and latitude of Nanjing. Use
center
scale
This method belongs to manually adjusting the map to display the map properly. However, the official also provides a tool function that can automatically layout and zoom the map to adapt to the svg size –fitExtent
, you can pass in a two-digit array to define the upper left and lower right corners of your canvasFor example we can write:
const projection = d3. geoMercator() .fitExtent([[0, 0], [svgW, svgH]], mapJson)
or use the shorthand:
const projection = d3. geoMercator() .fitSize([svgW, svgH], mapJson)
fitSize
sets the first coordinate data to 0,0 by defaultThe second parameter needs a complete
geoJson
object, note that it is notgeo.features
so we don’t needcenter
andscale
, and they cannot be called afterfitExtent
, an error will be reported. -
Generate route data Draw maps
const pathGenerator = d3. geoPath() .projection(projection)
Pass our configured projection parameters to the
projection
method ofgeoPath
, and you can get thed
attribute of thepath
tag : MDN_pathThen we bind the data,
d3.data()
usesgeoJson.features
instead of the wholegeoJson
Be sure to pay attention! In terms of map color, just use an existing d3 color scheme to fill it. If you have other needs, you can understand the configuration by yourself. For specific tutorials, please refer to: https://github.com/d3/d3-scale-chromatic,const mapDraw = g. selectAll("path") .data(mapJson.features) //data binding mapDraw. join("path") .attr("class", "continent-path") .attr("d", pathGenerator) //draw path .attr("stroke-width", 1.60347) .attr("stroke", "#000") .style("fill", (d, i) => d3.schemeSet2[i % 3])
Then we can label our map with geographic names. The
projection
tool we configured can support passing in a set of geographic coordinates and return a set of two-dimensional coordinates for the positioning of Svg elements. We can usecentroid (centroid) in code>geoJson is used as our positioning benchmark, and it is enough to pass it to the transform
attribute after conversionmapDraw. join('text').attr("class", "continent-text") .attr('transform', (d) => {<!-- --> return `translate(${<!-- -->projection(d.properties.centroid)})` }) .text((d) => {<!-- --> return d.properties.name })
If your map only has a small dot, you may have set the wrong center
data, be careful not to pass the latitude and longitude backwards
There is a pit you may step on:
Maybe in the fifth step, you will find that the map cannot be rendered, and you may encounter the following problems:
If you download map data from the Alibaba Cloud data platform like me, you may encounter the same problem as me, that is, using center
and scale
can be displayed normally Map, after using fitExtent
the map is a small point or not displayed at all, this is the first pit I encountered:
Because D3 uses ellipsoidal math
to calculate the projection of the map, instead of using the Cartesian coordinate system to process the data of geoJson
like other tools, and D3 uses the right-hand rule to draw the map, and The existing geoJson specification is the opposite, which will result in the drawing area may also be “inverted”, as shown in the following figure:
[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-n7CXTm8A-1678532273824)(null)]
[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-dN29lk9Z-1678532273807)(null)]
I am not a related professional, if you need a more detailed explanation, you can move => https://github.com/d3/d3-geo/pull/79#issuecomment-280935242
Solving this problem is also very simple, we can use the turf tool – a geospatial data analysis and processing tool that can be used in browsers and Node to perform conversion,
We need to use the rewind
function to ‘rewind’ our data, as the name implies, it is the rewinding operation in the tape, and the reverse winding operation on the geo data,\
Install the tools you need as needed:
npm i @turf/rewind
Process the geoJson
data before drawing in D3:
mapJson.features = mapJson.features.map(function (feature) { return turf. rewind(feature, { reverse: true }); })
Next, use fitExtent to draw the map. Let me compare the effect of manually setting parameters and using fitExtent
[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-nKumrjim-1678532273788)(null)]
Manually adjust the zoom and centering problem. It is really difficult to find the appropriate size and offset value. It is better to adjust automatically
Here is the complete code:
Document <body> <svg class="map" style="display: inline-block;"> </svg> </body>
All files here => https://github.com/3biubiu/D3ToDrawMap