你可能不知道的前端算法之文字避讓(inMap)
前言
inMap 是一款基于 canvas 的大數(shù)據(jù)可視化庫(kù),專注于大數(shù)據(jù)方向點(diǎn)線面的可視化效果展示。目前支持散點(diǎn)、圍欄、熱力、網(wǎng)格、聚合等方式;致力于讓大數(shù)據(jù)可視化變得簡(jiǎn)單易用。
GitHub 地址:https://github.com/TalkingData/inmap(本地下載)
文檔地址:http://inmap.talkingdata.com/
在地理信息可視化中,我們經(jīng)常會(huì)遇到在地圖上標(biāo)記文字的需求,下面展示的是某流行 chart 圖表框架的效果:

要顯示的文字空間不夠時(shí),就會(huì)造成文字重疊顯示混亂,用戶體驗(yàn)很不友好。
怎么解決這個(gè)問(wèn)題呢?我們采用文字避讓算法,解決這種坑爹的問(wèn)題。
下面展示的是 inMap 文字避讓效果:

文字標(biāo)注算法是 GIS 中最復(fù)雜的問(wèn)題之一(屬于 NP 復(fù)雜度問(wèn)題,所以通常不能找到最優(yōu)解,只能找到較優(yōu)解)。
inMap 避讓算法采用的是四分位模型算法,接下來(lái)手把手教你寫避讓算法,老司機(jī)帶你裝逼帶你飛。
準(zhǔn)備數(shù)據(jù)
inMap 接收的是經(jīng)緯度數(shù)據(jù),需要把它映射到 canvas 的像素坐標(biāo),這就用到了墨卡托轉(zhuǎn)換,墨卡托算法很復(fù)雜,以后我們會(huì)有單獨(dú)的一篇文章來(lái)講講他的原理。經(jīng)過(guò)轉(zhuǎn)換,你得到的數(shù)據(jù)應(yīng)該是這樣的:
[
{
"name": "海門",//要顯示的文字
"lng": 121.15,
"lat": 31.89,
"count": 7,
"pixel": { //像素坐標(biāo)
"x": 968,
"y": 736
}
},
{
"name": "鄂爾多斯",
"lng": 109.781327,
"lat": 39.608266,
"count": 5,
"pixel": {
"x": 659,
"y": 478
}
},
...
]
好了,我們得到轉(zhuǎn)換后的像素坐標(biāo)數(shù)據(jù)(x、y),就可以做下面的事情了。
求出每段文字矩形的實(shí)際大小
measureText() 是 canvas 內(nèi)置的方法,返回字體寬度的像素單位:
let ctx = this.container.getContext('2d'); // canvas 上下文
let width= ctx.measureText(name).width;
我們通過(guò) measureText 得到每個(gè)文字的寬度,canvas 并沒有直接獲取文字的方法,那文字的高度如何的得到呢?
我們通過(guò)反復(fù)測(cè)試發(fā)現(xiàn) canvas 的 font 等于 “13px Arial” 字體(別的字體不敢保證)的時(shí)候,文字的高度大概是 fontSize 的 1.1 倍。
所以代碼如下:
let fontSize = parseInt(ctx.font); let height = fontSize * 1.1;
文字的寬度和高度得到后,我們就可以創(chuàng)建文字矩形的坐標(biāo)系了。
創(chuàng)建四分位模型

所謂四分位模型,每一個(gè)標(biāo)記點(diǎn)都有上下左右四個(gè)放文字的位子,如果左邊放不下,那就放右邊試試,還不行就放到下面試試,以此類推,原理就這么簡(jiǎn)單,哈哈。
創(chuàng)建右側(cè)虛擬矩形坐標(biāo)描述:

右側(cè)虛擬矩形坐標(biāo)的描述把圓點(diǎn)也包含在內(nèi)了,是為了防止文字和圓點(diǎn)重疊。
在計(jì)算虛擬矩形的高度時(shí)有些坑,圓點(diǎn)大小不是固定的,是根據(jù)用戶動(dòng)態(tài)配置的,圓點(diǎn)的直徑可能大于文字的高度,我們就設(shè)定虛擬矩形的高度永遠(yuǎn)都是最大的那個(gè),需要做一些特殊處理。
代碼如下:
_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
};
}
以此類推,描述下面、左面、上面的虛擬矩形坐標(biāo)。
判斷碰撞
判斷兩個(gè)矩形是否覆蓋相交,根據(jù)矩形的 minX,maxX,minY,maxY 判斷相交,原理比較簡(jiǎn)單,代碼如下:
/**
* 判斷分位是否相交
* @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;
}
創(chuàng)建虛擬文字集合對(duì)象
let labels = pixels.map((val) => {
let radius = val.pixel.radius + this.style.normal.borderWidth; //圓點(diǎn)半徑
return new Label(val.pixel.x, val.pixel.y, radius, fontSize, byteWidth, val.name);
});
遞歸遍歷虛擬文字集合、判斷是否與其他相交,如果有相交就移動(dòng)當(dāng)前文字位子,直到不相交為止。當(dāng)找不到合適位置時(shí),就選擇隱藏當(dāng)前文字。
代碼如下:
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();
}
});
文字避讓算法到目前介紹完了,對(duì)應(yīng)的 inMap 文件地址為https://github.com/TalkingData/inmap/blob/master/src/worker/helper/Label.js,接下來(lái)還會(huì)繼續(xù)給大家分享干貨。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
微信小程序登錄按鈕遮罩浮層效果的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于微信小程序登錄按鈕遮罩浮層效果的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12
JavaScript中如何使用cookie實(shí)現(xiàn)記住密碼功能及cookie相關(guān)函數(shù)介紹
cookie是網(wǎng)站設(shè)計(jì)者放置在客戶端(瀏覽器)的小文本文件,cookie不僅能夠?qū)崿F(xiàn)保存密碼功能,還可以通過(guò)cookie保存最近瀏覽記錄增加用戶體驗(yàn)。本文給大家介紹js使用cookie實(shí)現(xiàn)記住密碼功能及cookie相關(guān)函數(shù)講解,感興趣的朋友一起看看吧2016-11-11
JavaScript canvas實(shí)現(xiàn)加載圖片
這篇文章主要為大家詳細(xì)介紹了JavaScript canvas實(shí)現(xiàn)加載圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
基于javascript的在火狐里面強(qiáng)制換行字符
火狐也不知道為什么,自己功能上,樣式上面,都無(wú)法讓其強(qiáng)制換行,這里用JS來(lái)實(shí)現(xiàn)火狐里面強(qiáng)制換行的方法。2010-06-06
uniapp返回上一頁(yè)并實(shí)現(xiàn)刷新界面數(shù)據(jù)的完整代碼
從一個(gè)列表界面點(diǎn)擊新增按鈕,進(jìn)入新增元素的界面,然后新增之后返回列表界面,并刷新列表界面,下面小編給大家分享uniapp返回上一頁(yè),并實(shí)現(xiàn)刷新界面數(shù)據(jù)的代碼,感興趣的朋友跟隨小編一起看看吧2024-04-04
bootstrap 通過(guò)加減按鈕實(shí)現(xiàn)輸入框組功能
這篇文章主要介紹了bootstrap 輸入框組 通過(guò)加減按鈕來(lái)增加刪除內(nèi)嵌輸入框組,當(dāng)我點(diǎn)擊 + 按鈕時(shí),會(huì)添加一行輸入框組;當(dāng)點(diǎn)擊 - 按鈕時(shí),會(huì)刪除這一行輸入框組。具體實(shí)現(xiàn)代碼大家參考下本文2017-11-11
Bootstrap打造一個(gè)左側(cè)折疊菜單的系統(tǒng)模板(一)
這篇文章主要介紹了Bootstrap打造一個(gè)左側(cè)折疊菜單的系統(tǒng)模板(一)的相關(guān)資料,需要的朋友可以參考下2016-05-05

