最近做的一个d3地图工作,需要是画出中国地图,在指定区域显示亮色并在指定城市显示光晕动画,做下笔记备忘。
标几个将来可能会再次遇到的需要点:
地图中指定区域设置色彩
通过.style(fill) 回调返回自定义填充色,指定区域上的text也可通过此办法自定义色彩
let gmap = svg.append("g")gmap.selectAll("path") .data(mapdata) .enter() .append("path") .style("fill", (d, i, a) => { if (Object.keys(cityordinate).includes(d.properties.name)) { return 'rgba(29, 223, 201, 1)' } return "rgba(62, 241, 230, .4)" });
光晕动画
用d3.interval 设置circle的大小和透明度;d3.scaleLinear设置映射参数和对应值的区间,用时间差取余作为参数造成3000毫秒一周期,绘制光晕成果
var scale = d3.scaleLinear(); scale.domain([0, 1800, 3000])//设置淡>亮>淡的趋势 .range([0, 0.9, 0]); var scale2 = d3.scaleLinear(); scale2.domain([0, 3000]) .range([0, 8]);//设置从小变大的趋势 var start = Date.now(); this.dInter = d3.interval(function () { var s1 = scale((Date.now() - start) % 3000); var s2 = scale2((Date.now() - start) % 3000) < 0 ? -scale2((Date.now() - start) % 3000) : scale2((Date.now() - start) % 3000); gmap.select("circle#hangzhouC") .attr("fill-opacity", s1) .attr("r", s2) marker.select("circle#markerC") .attr("fill-opacity", s1) .attr("r", s2) }, 100);
画两点射线
- 调用之前建设的project()办法,将起点经纬度转换为平面坐标。
- 计算终点(长沙)和起点之前的间隔,做为线条长度和动画工夫参数。
- 在线条上绘制一个圆形标记,并实现从终点到起点的挪动动画。
- 标记挪动到起点后,即删除,节俭资源。
// 创立办法,输出data坐标,绘制发射线,此处按需要把连线stroke设为通明 let mline = svg.append("g").attr("id", "moveto").attr("stroke", "rgba(255, 192, 67, 0)").attr("stroke-width", 1.5).attr("fill", "#FFC043"); var moveto = function (city, data) { var pf = { x: projection([120.21, 30.25])[0], y: projection([120.21, 30.25])[1] }; var pt = { x: projection(data)[0], y: projection(data)[1] }; var distance = Math.sqrt((pt.x - pf.x) ** 2 + (pt.y - pf.y) ** 2); mline.append("line") .attr("x1", pf.x) .attr("y1", pf.y) .attr("x2", pt.x) .attr("y2", pt.y) .attr("marker-end", "url(#pointer)") //设置线尾标识款式 // .style("stroke-dasharray", " " + distance + ", " + distance + " ") //可设置虚线然而没有画线成果了 .transition() .duration(distance * 30) //继续时长 .styleTween("stroke-dashoffset", function () { return d3.interpolateNumber(distance, 0); }); mline.append("circle") .attr("cx", pf.x) .attr("cy", pf.y) .attr("r", 2) .transition() .duration(distance * 30) .attr("transform", "translate(" + (pt.x - pf.x) + "," + (pt.y - pf.y) + ")") .remove(); //射线头的款式,射线达到指标后移出线头的圆 };
残缺的代码
initMap = async () => { let cityordinate = await this.getAllJoinMedical() //获取已入驻城市数据 var width = 750.43 * screenWidth / 1920, height = screenWidth <= minWidth ? 620 * screenHeight / 1080 : 690 * screenHeight / 1080; // 定义SVG宽高 let svg if (!this.svg) { //因为父组件会刷新,这里避免画出第二个地图 svg = d3.select(".fxmap") .append("svg") .attr("width", width) .attr("height", height) } else { svg = this.svg } const defs = svg.append("defs"); //插入defs const linearGradient = defs //defs中插入<linearGradient> .append("linearGradient") .attr("id", "gradient"); //设置对应id linearGradient //linearGradient中插入stop元素 .append("stop") .attr("offset", "0%") //设置坡度,下同 .attr("stop-color", '#60F3F9');//设置对应色彩,下同 linearGradient //linearGradient中插入stop元素 .append("stop") .attr("offset", "20%") //设置坡度,下同 .attr("stop-color", '#60F3F9');//设置对应色彩,下同 linearGradient //linearGradient中插入stop元素 .append("stop") .attr("offset", "98%") //设置坡度,下同 .attr("stop-color", '#19B2BC');//设置对应色彩,下同 const lineDefs = svg.append("defs"); //插入defs const linearGradient2 = lineDefs //defs中插入<linearGradient> .append("linearGradient") .attr("id", "lineGradient"); //设置对应id linearGradient2 //linearGradient中插入stop元素 .append("stop") .attr("offset", "0%") //设置坡度,下同 .attr("stop-color", '#60F3F9');//设置对应色彩,下同 linearGradient2 //linearGradient中插入stop元素 .append("stop") .attr("offset", "20%") //设置坡度,下同 .attr("stop-color", '#60F3F9');//设置对应色彩,下同 linearGradient2 //linearGradient中插入stop元素 .append("stop") .attr("offset", "98%") //设置坡度,下同 .attr("stop-color", '#19B2BC');//设置对应色彩,下同 this.svg = svg let gmap2 = svg.append("g").attr("id", "map2") //因为南海诸岛面积小不显色,边框又和背景色彩雷同,故重建画布用填充色画边框,将南海数据抽出填进去 .attr("stroke", 'rgba(62, 241, 230, .4)').attr("stroke-width", 1) .attr("fill", "rgba(62, 241, 230, .4)"); let gmap = svg.append("g").attr("id", "map") .attr("stroke", 'rgba(4, 23, 62, .6)').attr("stroke-width", 1) // var projection = d3.geoEquirectangular() var projection = d3.geoMercator() //不同投影函数成果不同 .center([465, 395]) // 指定投影核心,留神[]中的是经纬度 .scale(height - 50) //依据什么进行缩放 .translate([width / 2 + 17, screenWidth <= minWidth ? height / 2 - 10 : height / 2 - 45]); //图形在画布中偏移设置 // var projection = d3 // .geoStereographic()//球形投影 // .center([465, 395]) // 指定投影核心,留神[]中的是经纬度 // .scale(height) // // .clipAngle(180) // .translate([width / 2, height / 2+20]) var path = d3.geoPath().projection(projection); let marker = svg.append("defs") .append("marker") .append("marker") .attr("id", "pointer") .attr("viewBox", "0 0 24 24") // 可见范畴 .attr("markerWidth", "24") // 标记宽度 .attr("markerHeight", "24") // 标记高度 .attr("orient", "auto") // .attr("markerUnits", "strokeWidth") // 随连接线宽度进行缩放 .attr("refX", "6") // 连接点坐标 .attr("refY", "6") // 绘制标记核心圆 marker.append("circle") .attr("cx", "6") .attr("cy", "6") .attr("r", "2") .attr("fill", "#FFC043"); // 绘制标记外圆,之后在timer()中增加闪动成果 marker.append("circle") .attr("id", "markerC") .attr("cx", "6") .attr("cy", "6") .attr("r", "0") .attr("fill-opacity", "0") .attr("fill", "#FFC043") // 记录杭州坐标 var hangzhou = projection([120.219375416, 30.2592444615]); // 读取地图数据,并绘制中国地图 let mapdata = []; // 读取地图数据 mapdata = chinaJson.features; let mapdata2 = chinaJson2.features; // 绘制地图 gmap2.selectAll("path") //画行政区边界 .data(mapdata2) .enter() .append("path") .attr("d", path) .style("fill", (d, i, a) => { if (Object.keys(cityordinate).includes(d.properties.name)) { //已入驻地图显示另一种色彩 return 'rgba(29, 223, 201, 1)' } return "rgba(62, 241, 230, .4)" }); gmap.selectAll("path") .data(mapdata) .enter() .append("path") .attr("d", path) .style("fill", (d, i, a) => { if (Object.keys(cityordinate).includes(d.properties.name)) { return 'rgba(29, 223, 201, 1)' } return "rgba(62, 241, 230, .4)" }); svg .selectAll('text') .data(mapdata) .enter() .append('text') //显示行政区名 .text((d, i) => { return d.properties.name }) .attr('font-size', 14) .attr("x", function (d) { let xNum = d.properties.centroid && Number(projection(d.properties.centroid)[0]) - (d.properties.name.length) * 14 / 2 || 0 if (d.properties.name == '香港') return xNum + 20 return xNum }) .attr("y", function (d) { let yNum = d.properties.centroid && projection(d.properties.centroid)[1] || 0 if (d.properties.name == '内蒙古') return yNum + 20 if (d.properties.name == '澳门') return yNum + 12 return yNum }) .attr("stroke-width", 0) .style('fill', (d, i, a) => { if (Object.keys(cityordinate).includes(d.properties.name)) { return 'rgba(3, 2, 27, 1)' } return "rgba(29, 223, 201, 1)" }) // 标记杭州 gmap.append("circle").attr("id", "hangzhou") .attr("cx", hangzhou[0]) .attr("cy", hangzhou[1]) .attr("r", "4") .attr("fill", "#FFC043") gmap.append("circle").attr("id", "hangzhouC") .attr("cx", hangzhou[0]) .attr("cy", hangzhou[1]) .attr("r", "0") .attr("stroke-width", "2") .attr("fill-opacity", "0") .attr("fill", "#FFC043") .attr("stroke-opacity", 0) .attr("stroke", "#FFC043") // 创立办法,输出data坐标,绘制发射线,此处按需要把连线stroke设为通明 let mline = svg.append("g").attr("id", "moveto").attr("stroke", "rgba(255, 192, 67, 0)").attr("stroke-width", 1.5).attr("fill", "#FFC043"); var moveto = function (city, data) { var pf = { x: projection([120.21, 30.25])[0], y: projection([120.21, 30.25])[1] }; var pt = { x: projection(data)[0], y: projection(data)[1] }; var distance = Math.sqrt((pt.x - pf.x) ** 2 + (pt.y - pf.y) ** 2); mline.append("line") .attr("x1", pf.x) .attr("y1", pf.y) .attr("x2", pt.x) .attr("y2", pt.y) .attr("marker-end", "url(#pointer)") //设置线尾标识款式 // .style("stroke-dasharray", " " + distance + ", " + distance + " ") //可设置虚线然而没有画线成果了 .transition() .duration(distance * 30) //继续时长 .styleTween("stroke-dashoffset", function () { return d3.interpolateNumber(distance, 0); }); mline.append("circle") .attr("cx", pf.x) .attr("cy", pf.y) .attr("r", 2) .transition() .duration(distance * 30) .attr("transform", "translate(" + (pt.x - pf.x) + "," + (pt.y - pf.y) + ")") .remove(); //射线头的款式,射线达到指标后移出线头的圆 }; gmap2 .selectAll('text') .data(mapdata2) .enter() .append('text') .text((d, i) => { return d.properties.name }) .attr('font-size', 14) .attr("x", function (d) { let xNum = d.properties.centroid && Number(projection(d.properties.centroid)[0]) - (d.properties.name.length) * 14 / 2 || 0 return xNum }) .attr("y", function (d) { let yNum = d.properties.centroid && projection(d.properties.centroid)[1] || 0 return yNum + 5 }) .attr("stroke-width", 0) .style('fill', (d, i, a) => { if (Object.keys(cityordinate).includes(d.properties.name)) { return 'rgba(3, 2, 27, 1)' } return "rgba(29, 223, 201, 1)" }) // .style('fill', 'rgba(29, 223, 201, 1)') var scale = d3.scaleLinear(); //映射函数 scale.domain([0, 1800, 3000])//映射区间,参数传入1800时返回0.9,设置淡>亮>淡的趋势 .range([0, 0.9, 0]); var scale2 = d3.scaleLinear(); scale2.domain([0, 3000]) .range([0, 8]);//设置从小变大的趋势 var start = Date.now(); this.dInter = d3.interval(function () { var s1 = scale((Date.now() - start) % 3000); //三秒为周期由小到大循环 var s2 = scale2((Date.now() - start) % 3000) < 0 ? -scale2((Date.now() - start) % 3000) : scale2((Date.now() - start) % 3000); gmap.select("circle#hangzhouC") .attr("fill-opacity", s1) .attr("r", s2) marker.select("circle#markerC") .attr("fill-opacity", s1) .attr("r", s2) }, 100); for (var key in cityordinate) { if (key == '杭州') continue moveto(key, cityordinate[key]); //发射线 }; }
技术过程借鉴:无鱼二饼