通过使用鱼眼视图,在大尺度下被弱化的细节信息被显著强化,使得可视分析用户可以在大尺度下同时兼顾细节信息。
工具
d3.js
d3.geo.js
d3-plugin fisheye.js
china.json
实现代码 index.html 1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html > <html > <head > <meta charset ="utf-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1, maximum-scale=1" > <title > SvgEarthDemo</title > </head > <body > <svg id ="mysvg" width ="3000" height ="1500" > </svg > </body > </html >
获取中国地图轮廓经纬度 china.json 是 GeoJson 格式的文件
1 2 3 4 5 function getChinaJson ( ) { let chinaText = $.ajax({url :"/json/china.json" ,async :false }) let chinaJson = decode(JSON .parse(chinaText.responseText)) return chinaJson }
使用 d3.geo 在 svg 中绘制中国地图 d3.geoPath 绘制中国地图 使用 d3.geoPath 直接处理 GeoJson 对象,在 svg 中追加 path。 鱼眼畸变计算是针对节点的,在生成的 path 中很难找到具体的形状对象,该方式虽然简单易生成矢量地图但不适合进行鱼眼视图的转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 let projection = d3.geoMercator().center([70.591796875 , 55.494140625 ]).scale(1500 ).translate([0 ,0 ]).rotate([20 ])let chinaJson = getChinaJson()let path = d3.geoPath().projection(projection)let graticule = d3.geoGraticule()graticule.extent([[71 ,16 ],[137 ,54 ]]).step([2 ,2 ]) let grid = graticule()svg.append("path" ) .datum(grid) .attr("class" , "graticule" ) .style("stroke" , "#000" ) .attr("d" , path) let svg = d3.select('#mysvg' )svg.append("g" ) .selectAll("path" ) .data(chinaJson.features) .enter() .append("path" ) .attr("class" , "province" ) .attr("d" , path)
svg line 绘制中国地图 通过解析 GeoJson 对象,获取各个点经纬度坐标,通过墨卡托投影转化成 svg 坐标,在 svg 中利用点连成线绘制 line。 在生成的 line 中可获取到起始位置 ( x1, y1, x2, y2 ) ,该方式虽然生成矢量地图较复杂但易于进行鱼眼视图的转换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 let projection = d3.geoMercator().center([70.591796875 , 55.494140625 ]).scale(1500 ).translate([0 ,0 ]).rotate([20 ])let chinaJson = getChinaJson()let vector2ChinaJson = getVector2ChinaJson()function getVector2ChinaJson ( ) { let data = [] chinaJson.features.forEach(areas => { var areasData = { properties: [], coordinates: [] } areasData.properties = areas.properties areas.geometry.coordinates.forEach((points,i )=> { var arr = [] points.forEach((point,j )=> { if (point[0 ] instanceof Array ){ var a = [] point.forEach(p => { a.push(lnglatToMercator(p)) }) arr.push(a) }else { arr.push(lnglatToMercator(point)) } }) areasData.coordinates.push(arr) }) data.push(areasData) }) return data } function lnglatToMercator (point ) { var points = [] var point = [point] var point = projection(point) console .log() points.push(point) return points } function drawChina ( ) { svg.append("g" ) for (let j in vector2ChinaJson){ if (vector2ChinaJson[j].coordinates[0 ][0 ][0 ] instanceof Array ){ vector2ChinaJson[j].coordinates.forEach( points => { for (let index = 0 ; index < points[0 ].length-1 ; index++){ svg.select("g" ) .append("line" ) .attr("x1" , points[0 ][index][0 ]) .attr("y1" , points[0 ][index][1 ]) .attr("x2" , points[0 ][index+1 ][0 ]) .attr("y2" , points[0 ][index+1 ][1 ]) .attr("stroke" , "#000" ) } }) }else { vector2ChinaJson[j].coordinates.forEach( points => { for (let index = 0 ; index < points.length-1 ; index++){ svg.select("g" ) .append("line" ) .attr("x1" , points[index][0 ]) .attr("y1" , points[index][1 ]) .attr("x2" , points[index+1 ][0 ]) .attr("y2" , points[index+1 ][1 ]) .attr("stroke" , "#000" ) } }) } } }
绑定鼠标滑动事件,更新鱼眼视图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 let r = 150 let fisheye = d3.fisheye.circular().radius(r).distortion(10 )let f = null var timeObj = null var changeFlag = false svg.on("mousemove" , function (event ) { if (changeFlag){ d3.selectAll(".isChange" ) .call(function (d ) { d._groups[0 ].forEach((line,j )=> { let lineObj = d3.select(line) lineObj.attr("x1" , lineObj._groups[0 ][0 ].x1old) .attr("y1" , lineObj._groups[0 ][0 ].y1old) .attr("x2" , lineObj._groups[0 ][0 ].x2old) .attr("y2" , lineObj._groups[0 ][0 ].y2old) .attr("class" , "" ) }) }) } if (timeObj){ clearTimeout(timeObj) } timeObj = setTimeout(() => { updateLines(event) }, 200 ) }) function updateLines (event ) { let pointer = d3.pointer(event) fisheye.focus(pointer) f = fisheye({x : pointer[0 ], y : pointer[1 ]}) svg.selectAll("line" ) .call(function (d ) { d._groups[0 ].forEach((line,j )=> { x1 = line.x1.animVal.value y1 = line.y1.animVal.value x2 = line.x2.animVal.value y2 = line.y2.animVal.value if (((Math .pow(x1-f.x, 2 )+Math .pow(y1-f.y, 2 ))<Math .pow(r, 2 ))||((Math .pow(x2-f.x, 2 )+Math .pow(y2-f.y, 2 ))<Math .pow(r, 2 ))){ fisheye1 = fisheye({x : x1, y : y1}) fisheye2 = fisheye({x : x2, y : y2}) d3.select(line) .property("x1old" , x1) .property("y1old" , y1) .property("x2old" , x2) .property("y2old" , y2) .attr("x1" , fisheye1.x) .attr("y1" , fisheye1.y) .attr("x2" , fisheye2.x) .attr("y2" , fisheye2.y) .attr("class" , "isChange" ) changeFlag = true } }) }) }
添加缓动效果 1 2 3 4 5 6 7 8 9 lineObj.attr("class" , "" ) .transition() .duration(300 ) .attr("x1" , lineObj._groups[0 ][0 ].x1old) .attr("y1" , lineObj._groups[0 ][0 ].y1old) .attr("x2" , lineObj._groups[0 ][0 ].x2old) .attr("y2" , lineObj._groups[0 ][0 ].y2old)
注意事项
避免大量的遍历
mouseover 和 mousemove 的区分