文章出處

分析需求

首先我們要實現的文字云效果如下:

由圖可知,該文字云的效果是,一個大標簽文字在區域中心,其它小標簽文字圍繞這個大標簽文字。
其中,這些文字有隨機的顏色。
除了大標簽文字,其它標簽文字大小也隨機。
然后,圍繞這個效果呢,想象一下火影忍者的輪回眼

其實就像一顆小石頭扔向湖面,泛起陣陣漣漪(圓圈)向外擴散。

之后,文字之間不能交叉,也就是說不能碰撞,那就是說不能重疊。
好了,讓我們擼起袖子

擼函數

由以上的分析,需要擼一個獲取隨機顏色的值函數。

function getRandomColor(){
  var arr = [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'];
  var color = "#";
  for(i=0;i<6;i++){
    var c = parseInt(Math.random()*16);
    c = arr[c];
    color = color + c;
  }
  return color;
}

再擼一個獲取隨機的文字大小函數。

function getRandomFontSize(){
  var arr = [28,30,34,40];
  var res = [];
  for (var i = 0, len = arr.length; i < len; i++) {
    var j = Math.floor(Math.random() * arr.length);
    res[i] = arr[j];
    arr.splice(j, 1);
  }
  return res[0];
}

以上兩個函數代碼簡單粗暴,不再說明。
既然最大的標簽文字待在區域中心,那么獲取區域的中心點坐標函數必不可少。

function getCenterPoint(DOMElement){
  var rect = DOMElement.getBoundingClientRect();
  var rectTop = rect.top;
  var rectLeft = rect.left;
  var ngx = Math.ceil(rect.width);
  var ngy = Math.ceil(rect.height);
  var center = [(ngx / 2) + rectLeft, (ngy / 2) + rectTop];

  return center;
}

一句話講清,長寬取各一半再加上區域自己的坐標即可。
接下來就是圍繞這個效果對應的函數,

function getPointsAtRadius(radius, center ,offsetY, multiple){
  var T = radius * 8;
  var t = T;
  var points = [];
  var offsetY = offsetY || 1;
  var multiple = multiple || 30;

  if (radius === 0) {
    points.push([center[0], center[1]]);
  }
  while (t) {
    points.push(
      [
        center[0] + (radius*multiple) * Math.cos( (t * 2 * Math.PI )/ T ),
        center[1] + (radius*multiple) * Math.sin( (t * 2 * Math.PI )/ T ) * offsetY,
      ]
    );
    t = t - 1;
  }

  return points;
}

初中學的三角函數派上用場,它用來獲取圓上點的橫坐標和縱坐標。
這里除了半徑(radius)和圓中心坐標(center)兩個必要參數,還加上了Y軸偏移(offsetY),和單位距離(multiple)參數。
其中Y軸偏移(offsetY)可用來縮小或擴大選取的Y坐標,從而改變生成的文字云形狀。
單位距離(multiple)參數,是確定以多少像素作為一個半徑單位。
這年代,屏幕分辨率都很大,不可能以單個像素進行畫圈圈吧。
然后我們也沒必要拿到圓上的每個點的坐標。
那我們拿圓上多少個坐標比較合適?
文字標簽是矩形,一個矩形可被八個矩形直接包圍。間接包圍n8個矩形。
故取n
8個坐標即可。

接下來是判斷兩個矩形是否相交

function isCorssRect(array1, array2){
  var Xa1 = array1[0][0];
  var Ya1 = array1[0][1];
  var Xa2 = array1[1][0];
  var Ya2 = array1[1][1];

  var Xb1 = array2[0][0];
  var Yb1 = array2[0][1];
  var Xb2 = array2[1][0];
  var Yb2 = array2[1][1];

  var Xc1 = Math.max(Xa1,Xb1);
  var Yc1 = Math.max(Ya1,Yb1);
  var Xc2 = Math.min(Xa2,Xb2);
  var Yc2 = Math.min(Ya2,Yb2);

  return (Xc1 <= Xc2 && Yc1 <= Yc2);
}

首先一個矩形可由左上角坐標和右下角坐標來定義。
那么兩個矩形相交,則表明兩個矩形的左上角坐標最大值 要小于等于 兩個矩形的右下角坐標最小值。
請看圖想象。

擼業務

有了以上幾個函數,我們就可以開始構思業務邏輯。
假設輸入的數據是一個數組,比如["紅樓夢","賈寶玉","林黛玉","薛寶釵","王熙鳳","李紈","賈元春","賈迎春","賈探春","賈惜春","秦可卿","賈巧姐","史湘雲","妙玉","賈政","賈赦","賈璉","賈珍","賈環","賈母","王夫人","薛姨媽","尤氏","平兒","鴛鴦","襲人","晴雯","香菱","紫鵑","麝月","小紅","金釧","甄士隱","賈雨村"]

  1. 我們要依次取出一個詞,并且計算這個詞的寬高。
  2. 通過圍繞函數獲取將要放置的坐標。
  3. 通過詞的寬高 和 將要放置的坐標,可以得到這個詞的左上角坐標和右下角坐標信息。
  4. 然后跟已畫上去的詞云左上角坐標,右下角坐標進行比較,看兩個矩形是否相交
  5. 不相交,則畫上去,并把它的左上角坐標,右下角坐標信息進行存儲。
  6. 相交,則回到第2步,獲取下一個將要放置的坐標。

具體代碼如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>草珊瑚的文字云</title>
    <style>
    body{
        margin: 0;
    }
    #html-canvas{
        /*position: absolute;
        left:10px;
        top: 100px;*/
        width: 1415px;
        height: 796px;
        background-color: rgb(240, 240, 240);
        position: relative;
    }
    </style>
</head>
<body>
    <div id="html-canvas"></div>
</body>
<script>
/**
 * 獲取畫布的中心點坐標
 @method getCenterPoint
 @param {HTMLDOMElement} DOMElement 通常是一個div
 @return {array} [x, y] 包含中心點坐標的數組
 */
function getCenterPoint(DOMElement){
  var rect = DOMElement.getBoundingClientRect();
  var rectTop = rect.top;
  var rectLeft = rect.left;
  var ngx = Math.ceil(rectLeft + rect.width);
  var ngy = Math.ceil(rectTop + rect.height);
  var center = [(ngx / 2) + rectLeft, (ngy / 2) + rectTop];
  console.log('rect', rect);
  return center;
}
// var tagCanvas = document.getElementById('html-canvas');
// console.log("所選DOM的中心點為",getCenterPoint(tagCanvas));

// 獲取最大半徑
function getMaxRadius(DOMElement, cellSpace){
  var cellSpace = cellSpace || 1;
  var rect = DOMElement.getBoundingClientRect();
  var ngx = Math.ceil(rect.width / cellSpace);
  var ngy = Math.ceil(rect.height / cellSpace);
  var maxRadius = Math.floor(Math.sqrt(ngx * ngx + ngy * ngy));

  return maxRadius;
}

/**
  * 獲取圓上每個點的坐標
  @method getCenterPoint
  @param {int} radius 半徑
  @param {array} center 類似[11,22]中心點
  @param {int} offsetY y坐標的偏移位置,默認為1
  @return {array} [x, y] 圓上坐標的數組
  */
function getPointsAtRadius(radius, center ,offsetY, multiple){
  // 因為像素是一個正方形,一個正方形的四周有8塊正方形可包圍。
  var T = radius * 8;
  var t = T;
  var points = [];
  var offsetY = offsetY || 1;
  var multiple = multiple || 30;

  if (radius === 0) {
    points.push([center[0], center[1]]);
  }
  while (t) {
    // 參考http://www.cnblogs.com/xieon1986/archive/2013/01/28/2880367.html
    // 圓上每個點的
    // Y坐標=圓心y坐標 + Math.sin(2*Math.PI / 360) * 半徑
    // X坐標=圓心x坐標 + Math.cos(2*Math.PI / 360) * 半徑 

    // 弧度=(2*PI/360)*角度
    // 基于圓心的x坐標 X坐標=圓心x坐標 + Math.sin((2*Math.PI / 360) * 1) * 半徑 
    // 這里的T同360°的意義
    points.push(
      [
        center[0] + (radius*multiple) * Math.cos( (t * 2 * Math.PI )/ T ),
        center[1] + (radius*multiple) * Math.sin( (t * 2 * Math.PI )/ T ) * offsetY,
      ]
    );

    t = t - 1;
  }

  return points;
}
// console.log('圓上各點坐標', getPointsAtRadius(1,[731.5, 475]));

/**
 * 獲取文本的寬高
 @method getCenterPoint
 @param {HTMLDOMElement} DOMElement 通常是一個div
 @return {array} [x, y] 包含中心點坐標的數組
 */
function getTextInfo(word, userCSS){
  var fontSize = getRandomFontSize();
  if(userCSS){
    fontSize = userCSS.fontSize;
  }

  // 通過canvas的API來獲取文本的各種信息
  var fcanvas = document.createElement('canvas');
  var fctx = fcanvas.getContext('2d');

  fctx.font = 'normal ' + fontSize + 'px Hiragino Mincho Pro, serif';

  // 通過canvas的measureText方法獲取文本 像素級別的寬度
  // http://www.w3school.com.cn/tags/canvas_measuretext.asp
  var fw = fctx.measureText(word).width;
  // 通過字體大小獲取高度
  var fh = fontSize;

  return {
    width: Math.ceil(fw),
    height: fh,
    word: word,
    fontSize: fontSize
  }
}
// console.log('你輸入的文字長寬為', getTextInfo('你好'));

/**
 * 判斷兩個矩形是否相交
 * 參考:http://www.cnblogs.com/avril/archive/2012/11/13/2767577.html
 * http://www.cnblogs.com/avril/archive/2013/04/01/2993875.html
 @method isCorssRect
 @param {array} 
 @param {array} 
 @return {bool} true表示有重疊,false表示沒有重疊
 */
function isCorssRect(array1, array2){
  var Xa1 = array1[0][0];
  var Ya1 = array1[0][1];
  var Xa2 = array1[1][0];
  var Ya2 = array1[1][1];

  var Xb1 = array2[0][0];
  var Yb1 = array2[0][1];
  var Xb2 = array2[1][0];
  var Yb2 = array2[1][1];

  var Xc1 = Math.max(Xa1,Xb1);
  var Yc1 = Math.max(Ya1,Yb1);
  var Xc2 = Math.min(Xa2,Xb2);
  var Yc2 = Math.min(Ya2,Yb2);

  return (Xc1 <= Xc2 && Yc1 <= Yc2);
}


//獲取隨機顏色的值
function getRandomColor(){
  var arr = [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'];
  var color = "#";
  for(i=0;i<6;i++){
    var c = parseInt(Math.random()*16);
    c = arr[c];
    color = color + c;
  }
  return color;
}

// 獲取隨機文字的大小
function getRandomFontSize(){
  var arr = [28,30,34,40];
  var res = [];
  for (var i = 0, len = arr.length; i < len; i++) {
    var j = Math.floor(Math.random() * arr.length);
    res[i] = arr[j];
    arr.splice(j, 1);
  }
  return res[0];
}

function drawText(position, textInfo, canvas ){
  var span = document.createElement('span');
  var styleRules = {
    'position': 'absolute',
    'display': 'block',
    'font': 'normal' + ' '+textInfo.fontSize+'px ' + 'Hiragino Mincho Pro, serif',
    'left': position[0] + 'px',
    'top': position[1] + 'px',
    'width': textInfo.width + 'px',
    'height': textInfo.height + 'px',
    'lineHeight': 1,
    'color': getRandomColor(),
  };
  span.textContent = textInfo.word;
  for (var cssProp in styleRules) {
    span.style[cssProp] = styleRules[cssProp];
  }
  canvas.appendChild(span);
}

/**
 * 獲取文本的左上右下坐標,
 @param {array} topLeft 類似
 @return {object} textInfo 包含中心點坐標的數組
 */
function getTopLeft(topLeft, textInfo){
  var left1 = topLeft[0];
  var top1 = topLeft[1];
 
  return [
    [left1, top1],
    [left1+ textInfo[0], top1 + textInfo[1]]
  ];
}


// 獲取可安置的坐標
function getDrawPosition(textInfo, maxRadius, center, cellSpace ,drawArray){
  var textInfo_width = textInfo.width;
  var textInfo_height = textInfo.height;
  var cellSpace = cellSpace || 10;
  for(var i=0; i<maxRadius; i++){
    var points = getPointsAtRadius(i, center, 0.64, cellSpace);
    pointsLoop:
    for(var j=0; j<points.length; j++){
      var topLeft = getTopLeft(points[j], [textInfo_width, textInfo_height]);
      // 是否和已存的文字碰撞
      for(var z =0 ; z< drawArray.length; z++){
        if(isCorssRect(topLeft, drawArray[z])){
          continue pointsLoop;
        }
        
      }
      drawArray.push(topLeft);
      return points[j];
    }
  }
  return null;
}


function start(){
  var tagCanvas = document.getElementById('html-canvas');
  var center = getCenterPoint(tagCanvas);
  var cellSpace = 20;
  var maxRadius = getMaxRadius(tagCanvas, cellSpace);
  var data = ["紅樓夢","賈寶玉","林黛玉","薛寶釵","王熙鳳","李紈","賈元春","賈迎春","賈探春","賈惜春","秦可卿","賈巧姐","史湘雲","妙玉","賈政","賈赦","賈璉","賈珍","賈環","賈母","王夫人","薛姨媽","尤氏","平兒","鴛鴦","襲人","晴雯","香菱","紫鵑","麝月","小紅","金釧","甄士隱","賈雨村"];
  var drawArray = [];
  var tempDrawPositionArray = [];

  for(var i =0; i< data.length; i++){
    var dataItem = data[i];
    var textInfo = getTextInfo(dataItem);
    var drawPosition = null;

    if(i != 0){
      drawPosition = getDrawPosition(textInfo, maxRadius, center, cellSpace, drawArray);
    }
    else{
      textInfo = getTextInfo(dataItem, {fontSize:60});
      drawPosition = getDrawPosition(
        textInfo, 
        maxRadius,
        [center[0]-(textInfo.width/2), center[1]-(textInfo.height/2)], 
        cellSpace, 
        drawArray
      );
    }

    if(drawPosition){
      tempDrawPositionArray.push([drawPosition, textInfo, tagCanvas]);
    }      
      
  }
  var timer = setInterval(function(){
    var textItem = tempDrawPositionArray.shift();
    if(textItem){
      drawText(textItem[0], textItem[1], textItem[2]);
    }
    else{
      clearInterval(timer);
    }
  }, 0);

  
}
start();
</script>
</html>

本文實現思路和實驗數據參考了wordcloud2
并重寫其百分之九十的代碼。
意在理解其思路。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()