最近做的一个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]); //发射线
    };
  }

技术过程借鉴无鱼二饼


周游宇内
0 声望0 粉丝

下一篇 »
浅谈小程序