首頁  >  文章  >  web前端  >  前端演算法中有關文字避讓的問題(詳細教學)

前端演算法中有關文字避讓的問題(詳細教學)

亚连
亚连原創
2018-06-12 14:34:142521瀏覽

這篇文章主要為大家介紹了關於前端演算法之文字避讓的相關資料,對於這個知識相信很多的朋友都不知道,但看到效果會驚嘆不已,實現這一個效果主要利用的是inMap文字避讓功能,文中透過範例程式碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起學習學習吧。

前言

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,接下來也會繼續跟大家分享乾貨。

上面是我整理給大家的,希望今後對大家有幫助。

相關文章:

在Vue中如何實現表頭和首列固定

express multer如何實作圖片上傳功能

原生JS如何實作音樂播放器

 在VueJS中如何設定使用者權限

在微信小程式中如何使用YDUI的ScrollTab元件

以上是前端演算法中有關文字避讓的問題(詳細教學)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn