最近做的一个 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]); // 发射线
};
}
技术过程借鉴:无鱼二饼