• 实现一个简单的文字云


    分析需求

    首先我们要实现的文字云效果如下:

    由图可知,该文字云的效果是,一个大标签文字在区域中心,其它小标签文字围绕这个大标签文字。
    其中,这些文字有随机的颜色。
    除了大标签文字,其它标签文字大小也随机。
    然后,围绕这个效果呢,想象一下火影忍者的轮回眼

    其实就像一颗小石头扔向湖面,泛起阵阵涟漪(圆圈)向外扩散。

    之后,文字之间不能交叉,也就是说不能碰撞,那就是说不能重叠。
    好了,让我们撸起袖子

    撸函数

    由以上的分析,需要撸一个获取随机颜色的值函数。

    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;*/
             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 {
         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
    并重写其百分之九十的代码。
    意在理解其思路。

  • 相关阅读:
    Java中ArrayList与LinkedList的区别
    Java中String、StringBuffer、StringBuilder的区别
    Java中的String类能否被继承?为什么?
    JAVA有哪些数据类型?基本数据类型各占多少个字节
    Linux远程复制命令SCP
    CentOS添加用户并加入sudo权限
    nginx配置反向代理解决前后端分离跨域问题
    执行ssh-add时出现Could not open a connection to your authentication agent
    Git 修改源地址
    ssh配置git clone简易流程
  • 原文地址:https://www.cnblogs.com/samwu/p/6473071.html
Copyright © 2020-2023  润新知