用d3制作一個基於GeoJSON的中國地圖


基礎知識

地圖數據

數據格式有兩種:GeoJSON,TopoJSON。其中,GeoJSON是描述地理信息的一種基本格式,TopoJSON是作者制定的格式。我主要了解GeoJSON。

獲取數據

可自行百度
中國的數據chain.geo.json放在附錄里面

示例

{
	"type": "FeatureCollection",
	"features": [
        {
			"type": "Feature",
			"id": "shang_hai",
			"properties": {
				"name": "上海",
				"cp": [121.4648, 31.2891],
				"childNum": 19
			},
			"geometry": {
				"type": "Polygon",
				"coordinates": [[[120.9375, 31.0254],[121.2012, 31.4648],
				[121.377, 31.5088],[121.1133, 31.7285],[121.2012, 31.8604],[121.9922, 31.5967],
				[121.9043, 31.1572],[121.9922, 30.8057],[121.2891, 30.6738],[120.9375, 31.0254]
					]
				]
			}
		}, {
			"type": "Feature",
			"id": "xiang_gang",
			"properties": {
				"name": "香港",
				"cp": [114.2578, 22.3242],
				"childNum": 1
			},
			"geometry": {
				"type": "Polygon",
				"coordinates": [.....]
			}
		},
		......
		]
		"srcSize": {
		"left": 243.4766,
		"top": 36.4307,
		"width": 61.6113,
		"height": 35.4638
	}
}

GeoJSON數據解釋

它是用於描述地理空間信息的數據格式

  • FeatureCollection:特征集合
  • type:GeoJSON對象都有type屬性,type的值有多種
  • Feature:是type的值之一,Feature特征對象必須包含變量geometry,properties
  • geometry:幾何體
  • properties:值可以是任意JSON對象或null
  • Polygon:面
    所以china.geo.json文件里,有一個對象,該對象的type值為特征集合FeatureCollection,其成員Feature的每一項描述一個省的地理信息,特征對象properties里面包含該省的名稱,id號。geometry包含該省的地理信息。

地圖制作

在一個矩形區域中,划傷中國大陸和港澳台,再在矩形的右下角添加一個小框,里面繪制南海諸島。南海島嶼需要單獨制作。

基礎圖像

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>中國地圖</title>
		<script src="http://d3js.org/d3.v3.min.js"></script>
	</head>
	<body>
		<svg></svg>
	</body>
	<script>
		var width = 1000, height = 700;
		//1.定義投影和生成器
		//定義地圖投影
		var projection=d3.geo.mercator()
		.center([107,31]) //地圖中心位置,107是經度,31是緯度
		.scale(600) //設置縮放量
		.translate([width/2,height/2]); // 設置平移量
		
		//定義地理路徑生成器,使每一個坐標都會先調用此投影,才產生路徑值
		var path=d3.geo.path()
		.projection(projection);// 設定投影
		
		//3.請求china.geo.json數據,添加<path>,每個path用於繪制一個省的路徑
		
		
		//請求china.geo.json
		d3.json("china.geo.json",function(err,root){
			if(err){
				alert('數據請求失敗');
			}
			console.log(root);
			//創建svg
			
			
			var svg = d3.select("body")
			        .append("svg")
			        .attr("width", width)
			        .attr("height", height);
					
			var groups=svg.append("g");
			
			groups.selectAll("path")
			.data(root.features) // 綁定數據
			.enter()
			.append("path")
			.style("fill",'#404466')
			.attr("d",path);//使用路徑生成器
		})
		
		
	</script>
</html>

訪問本地json文件產生跨域問題

在瀏覽器打開頁面時,可能會有跨域問題,網上有很多解決方法,主要是使json文件和頁面在同一個域內。我使用的是phpstudy來解決這個問題。
效果:

移入某省顯示其他顏色

加入兩個事件:mouseover和mouseout

groups.selectAll("path")
			.data(root.features) // 綁定數據
			.enter()
			.append("path")
			.on('mouseover',function(d,i){
				
				d3.select(this)
				.style('fill','#2CD8FF')
				
			})
			.on('mouseout',function(d,i){
				d3.select(this)
				.style('fill', '#404466')
			})
			.style("fill",'#404466')//填充內部顏色
			.attr("d",path)//使用路徑生成器

還可以在這兩個事件中改變邊框顏色

做鼠標文字跟隨

想要有文字可以跟隨鼠標移動,需要獲取鼠標位置。為了方便使用,我們選用offsetX,offsetY,獲取方法mouseXY()。
然后根據這兩個位置繪制一個方框,里面填入相應的省份方法createFangkuang()。
還需要有三個事件:

  • mouseover:鼠標移入,創建跟隨方框
  • mousemove:鼠標移動,刪除上一個方框,根據新的鼠標位置創建新方框。
  • mouseout:鼠標移除,刪除方框

代碼:

.fangkuang{
			width: 100px;
			height: 100px;
			padding: 5px;
			
			opacity: 0.6;
			border:1px solid #fff;
			border-radius: 5px;
			
		}
		
		.......
		
        //返回當前鼠標位置
		function mouseXY(e){
			var e=e||window.event;
			return { "x": e.offsetX, "y": e.offsetY };
		}
		
		//移除方框
		function fangkuangRemove()
		{
			d3.select("#fangkuang1").remove();
			d3.select("#fangkuang2").remove();
		}
		//創建方框和文字
		function createFangkuang(svg,d)
		{
			
			let XY=mouseXY(event);
			svg.append("rect")
			        .attr("id", "fangkuang1")
			        .attr("x", XY.x)
			        .attr("y",XY.y)
					.attr("class","fangkuang")
		//創建顯示tooltip文本
		svg.append("text")
			        .attr("id", "fangkuang2")
			        .attr("x", XY.x+40)
			        .attr("y",XY.y+20)
			        .attr("text-anchor", "middle")
			        .attr("font-family", "sans-serif")
			        .attr("font-size", "14px")
			        .attr("font-weight", "bold")
			        .attr("fill", "#fff")
			        .text(d.properties.name);
		}
	......
	
	groups.selectAll("path")
			.data(root.features) // 綁定數據
			.enter()
			.append("path")
			.on('mouseover',function(d,i){
				d3.select(this)
				.style('fill','#2CD8FF');
				console.log('ss');
				createFangkuang(svg,d);				
			})
			.on('mousemove',function(d,i){
				fangkuangRemove();
				createFangkuang(svg,d);
			})
			.on('mouseout',function(d,i){
				d3.select(this)
				.style('fill', '#404466');
				fangkuangRemove();
			})
			.style("fill",'#404466')//填充內部顏色
			.attr("d",path)//使用路徑生成器

效果:

產生方框不停閃動的問題及解決辦法

在查看效果時那個跟隨方框不停閃動,效果很不好。根據觀察和測試,發現它不斷的觸發mouseover事件,然而按照邏輯應該是將鼠標放在一個新的省份時才會觸發它。

思考了一下事件冒泡的可能性:

  1. 可是這里沒有出現父子級都有相同事件的情況,所以這種情況排除。
  2. 又想會不會是在移動的過程中鼠標指向的元素可能不是 ,而變成了它的父節點svg或是移動到了rect,text內,導致鼠標暫時離開path,造成不斷觸發了mouseover,mouseout事件。經過檢查,發現確實是這樣,在向方框的方向移到是,鼠標移到了rect,text內,造成閃動。

解決方法:

  • 將offsetX改為clientX,這樣不會產生閃動的問題,但是clientX是鼠標到瀏覽器窗口的左上角,要想鼠標跟隨,需要計算,這樣不方便。
  • 將方框放到離鼠標有一段距離的地方,保持距離,讓鼠標在移動時不會移動到方框內。
  • 有一個css樣式:pointer-events:none;元素不再是鼠標事件的目標,鼠標不再監聽當前層而去監聽下面的層中的元素。

看情況可作出適當選擇。

代碼

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>中國地圖</title>
		<script src="http://d3js.org/d3.v3.min.js"></script>
	</head>
	<style>
		.fangkuang{
			width: 100px;
			height: 100px;
			padding: 5px;
			
			opacity: 0.6;
			border:1px solid #fff;
			border-radius: 5px;
			
		}
		#fangkuang1{
			pointer-events: none;
		}
		#fangkuang2{
			pointer-events: none;
		}
		
	</style>
	<body>
		<svg></svg>
	</body>
	<script>
		
		var width = 960, height = 700;
		//1.定義投影和生成器
		//定義地圖投影
		var projection=d3.geo.mercator()
		.center([107,31]) //地圖中心位置,107是經度,31是緯度
		.scale(600) //設置縮放量
		.translate([width/2,height/2]); // 設置平移量
		
		//定義地理路徑生成器,使每一個坐標都會先調用此投影,才產生路徑值
		var path=d3.geo.path()
		.projection(projection);// 設定投影
		
		//3.請求china.geo.json數據,添加<path>,每個path用於繪制一個省的路徑
		/**
		 * 獲取鼠標位置
		 * @param {Object} e 當前的元素對象
		 */
		function mouseXY(e){
			var e=e||window.event;
			return { "x": e.offsetX, "y": e.offsetY };
		}
		
		/**
		 * 刪除文字和方框
		 */
		function fangkuangRemove()
		{
			d3.select("#fangkuang1").remove();
			d3.select("#fangkuang2").remove();
		}
		/**
		 * 創建方框和框內文字
		 * @param {Object} svg 
		 * @param {Object} d
		 */
		function createFangkuang(svg,d)
		{
			
			let XY=mouseXY(event);
			svg.append("rect")
			        .attr("id", "fangkuang1")
			        .attr("x", XY.x)
			        .attr("y",XY.y)
					.attr("class","fangkuang")
			//創建顯示tooltip文本
			svg.append("text")
			        .attr("id", "fangkuang2")
			        .attr("x", XY.x+40)
			        .attr("y",XY.y+20)
			        .attr("text-anchor", "middle")
			        .attr("font-family", "sans-serif")
			        .attr("font-size", "14px")
			        .attr("font-weight", "bold")
			        .attr("fill", "#fff")
			        .text(d.properties.name);
		}
		//請求china.geo.json
		d3.json("http://localhost/china.geo.json",function(err,root){
			if(err){
				alert('數據請求失敗');
			}
			console.log(root);
			//創建svg
			var svg = d3.select("body")
			        .append("svg")
			        .attr("width", width)
			        .attr("height", height);
					
			var groups=svg.append("g");
			
			groups.selectAll("path")
			.data(root.features) // 綁定數據
			.enter()
			.append("path")
			.on('mouseover',function(d,i){
				d3.select(this)
				.style('fill','#2CD8FF');
				console.log('ss');
				createFangkuang(svg,d);				
			})
			.on('mousemove',function(d,i){
				fangkuangRemove();
				createFangkuang(svg,d);
			})
			.on('mouseout',function(d,i){
				d3.select(this)
				.style('fill', '#404466');
				fangkuangRemove();
			})
			.style("fill",'#404466')//填充內部顏色
			.attr("d",path)//使用路徑生成器
			
		})
	</script>
</html>

附錄

chain.geo.json:
鏈接:https://pan.baidu.com/s/1BAvg26KiPgzeNyrNh-YjHQ
提取碼:urpc


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM