资讯专栏INFORMATION COLUMN

你不知道的前端算法之文字避让

yedf / 1957人阅读

摘要:避让算法采用的是四分位模型算法,接下来手把手教你写避让算法,老司机带你装逼带你飞。创建四分位模型所谓四分位模型,每一个标记点都有上下左右四个放文字的位子,如果左边放不下,那就放右边试试,还不行就放到下面试试,以此类推,原理就这么简单,哈哈。

本文作者:TalkingData 可视化工程师李凤禄
编辑:Aresn

inMap 是一款基于 canvas 的大数据可视化库,专注于大数据方向点线面的可视化效果展示。目前支持散点、围栏、热力、网格、聚合等方式;致力于让大数据可视化变得简单易用。

GitHub 地址:https://github.com/TalkingData/inmap

文档地址:http://inmap.talkingdata.com/

在地理信息可视化中,我们经常会遇到在地图上标记文字的需求,下面展示的是某流行 chart 图表框架的效果:

要显示的文字空间不够时,就会造成文字重叠显示混乱,用户体验很不友好。

怎么解决这个问题呢?我们采用文字避让算法,解决这种坑爹的问题。

下面展示的是 inMap 文字避让效果:

文字标注算法是 GIS 中最复杂的问题之一(属于 NP 复杂度问题,所以通常不能找到最优解,只能找到较优解)。

inMap 避让算法采用的是四分位模型算法,接下来手把手教你写避让算法,老司机带你装逼带你飞。

准备数据

inMap 接收的是经纬度数据,需要把它映射到 canvas 的像素坐标,这就用到了墨卡托转换,墨卡托算法很复杂,以后我们会有多带带的一篇文章来讲讲他的原理。经过转换,你得到的数据应该是这样的:

[
  {
    "name": "海门",//要显示的文字
    "lng": 121.15,
    "lat": 31.89,
    "count": 7,
    "pixel": { //像素坐标
      "x": 968,
      "y": 736
    }
  },
  {
    "name": "鄂尔多斯",
    "lng": 109.781327,
    "lat": 39.608266,
    "count": 5,
    "pixel": {
      "x": 659,
      "y": 478
    }
  },
...
]

好了,我们得到转换后的像素坐标数据(x、y),就可以做下面的事情了。

求出每段文字矩形的实际大小

measureText() 是 canvas 内置的方法,返回字体宽度的像素单位:

let ctx = this.container.getContext("2d"); // canvas 上下文
let width= ctx.measureText(name).width;

我们通过 measureText 得到每个文字的宽度,canvas 并没有直接获取文字的方法,那文字的高度如何的得到呢?

我们通过反复测试发现 canvas 的 font 等于 “13px Arial” 字体(别的字体不敢保证)的时候,文字的高度大概是 fontSize 的 1.1 倍。

所以代码如下:

let fontSize = parseInt(ctx.font);
let height = fontSize * 1.1;

文字的宽度和高度得到后,我们就可以创建文字矩形的坐标系了。

创建四分位模型

所谓四分位模型,每一个标记点都有上下左右四个放文字的位子,如果左边放不下,那就放右边试试,还不行就放到下面试试,以此类推,原理就这么简单,哈哈。

创建右侧虚拟矩形坐标描述:

右侧虚拟矩形坐标的描述把圆点也包含在内了,是为了防止文字和圆点重叠。

在计算虚拟矩形的高度时有些坑,圆点大小不是固定的,是根据用户动态配置的,圆点的直径可能大于文字的高度,我们就设定虚拟矩形的高度永远都是最大的那个,需要做一些特殊处理。

代码如下:

_getLeftAnchor() {
    let x = this.center.x - this.radius - this.textReact.width,
        y = this.center.y - this.textReact.height / 2,
        diam = this.radius * 2,
        maxH = diam > this.textReact.height ? diam : this.textReact.height; //矩形的高度
    return {
        x,
        y,
        minX: x,
        maxX: this.center.x + this.radius,
        minY: this.center.y - maxH / 2,
        maxY: this.center.y + maxH / 2
    };
}

以此类推,描述下面、左面、上面的虚拟矩形坐标。

判断碰撞

判断两个矩形是否覆盖相交,根据矩形的 minX,maxX,minY,maxY 判断相交,原理比较简单,代码如下:

/**
 * 判断分位是否相交
 * @param {*} target 
 */
 
isAnchorMeet(target) {
    let react = this.getCurrentRect(),
        targetReact = target.getCurrentRect();
    if ((react.minX < targetReact.maxX) && (targetReact.minX < react.maxX) &&
        (react.minY < targetReact.maxY) && (targetReact.minY < react.maxY)) {
        return true;
    }
    return false;
}
创建虚拟文字集合对象
let labels = pixels.map((val) => {
    let radius = val.pixel.radius + this.style.normal.borderWidth; //圆点半径
    return new Label(val.pixel.x, val.pixel.y, radius, fontSize, byteWidth, val.name);
});

递归遍历虚拟文字集合、判断是否与其他相交,如果有相交就移动当前文字位子,直到不相交为止。当找不到合适位置时,就选择隐藏当前文字。

代码如下:

do {
    var meet = false; //本轮是否有相交
    for (let i = 0; i < labels.length; i++) {
        let temp = labels[i];
        for (let j = 0; j < labels.length; j++) {
            if (i != j && temp.show && temp.isAnchorMeet(labels[j])) {
                temp.next();
                meet = true;
                break;
            }
        }
    }
} while (meet);
绘画文字
labels.forEach(function (item) {
    if (item.show) { //是否显示
        let pixel = item.getCurrentRect();
        ctx.beginPath();
        ctx.fillText(item.text, pixel.x, pixel.y);
        ctx.fill();
    }
});

文字避让算法到目前介绍完了,对应的 inMap 文件地址为https://github.com/TalkingData/inmap/blob/master/src/worker/helper/Label.js,接下来还会继续给大家分享干货。

福利

分享两位业内大牛的前端课程:

Aresn 大神,开源了很优秀的前端 UI 组件库 iView,出版了《Vue.js实战》一书。向大家推荐他的课程,Vue.js实战系列教程,从本链接过去的打八折优惠,先到先得。

Chaos WebGL 专家,擅长 web 3D 开发。 主要有被《玩坏的地球系列课程》,很适合初学者。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90771.html

相关文章

  • 你不知道前端算法热力图实现

    摘要:目前支持散点围栏热力网格聚合等方式致力于让大数据可视化变得简单易用。如图表示,红色区域表示分析要素的密度大,而蓝色区域表示分析要素的密度小。实现热力原理读取每个像素的值透明度,做一个颜色映射。 本文作者:TalkingData 可视化工程师李凤禄编辑:Aresn 欢迎加入 QQ 群参与技术讨论:618308202 inMap 是一款基于 canvas 的大数据可视化库,专注于大数据方...

    zhaochunqi 评论0 收藏0
  • TalkingData 开源地理信息可视化框架 inMap

    摘要:本文作者可视化工程师李凤禄是可视化团队开源的一款基于的大数据可视化库,专注于大数据方向点线面的可视化效果展示。目前支持散点围栏热力网格聚合等方式致力于让大数据可视化变得简单易用。后续会输出创造更好的可视化图形和算法,并后续推出版本。 showImg(https://segmentfault.com/img/bV0yHB?w=1600&h=900); 本文作者:TalkingData 可...

    xeblog 评论0 收藏0
  • 阿里王刚谈自动驾驶:从单车智能到车路协同智能进化

    摘要:在今年的云栖大会上,阿里巴巴人工智能实验室除了发布服务机器人天猫精灵太空蛋太空梭天猫精灵车载版,还重点推出了车路协同智能自动驾驶车。秒看懂智慧物流车和感知基站各位嘉宾,上午好我是王刚,我要介绍的是自动驾驶从单车智能跨到协同智能的进化。 在今年的云栖大会上,阿里巴巴人工智能实验室除了发布AliGenie 3.0、服务机器人(天猫精灵太空蛋、太空梭)、天猫精灵车载版(TmallGenie ...

    shery 评论0 收藏0
  • 阿里王刚谈自动驾驶:从单车智能到车路协同智能进化

    摘要:在今年的云栖大会上,阿里巴巴人工智能实验室除了发布服务机器人天猫精灵太空蛋太空梭天猫精灵车载版,还重点推出了车路协同智能自动驾驶车。秒看懂智慧物流车和感知基站各位嘉宾,上午好我是王刚,我要介绍的是自动驾驶从单车智能跨到协同智能的进化。 在今年的云栖大会上,阿里巴巴人工智能实验室除了发布AliGenie 3.0、服务机器人(天猫精灵太空蛋、太空梭)、天猫精灵车载版(TmallGenie ...

    Dongjie_Liu 评论0 收藏0
  • css - 收藏集 - 掘金

    摘要:绝对底部前端掘金来自国外的设计达人,纯,可以实现当正文内容很少时,底部位于窗口最下面。有效解决图片使用单位边角缺失的问题前端掘金起因在移动端使用布局时图片也需要用单位。 CSS 绝对底部 - 前端 - 掘金来自国外的设计达人,纯CSS,可以实现: 当正文内容很少时,底部位于窗口最下面。当改变窗口高度时,不会出现重叠问题。甚至,创造该CSS的人还专门成立一个网站介绍这个CSS底部布局方案...

    phpmatt 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<