zoukankan      html  css  js  c++  java
  • 实现一个简单的文字云

    分析需求

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

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

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

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

    撸函数

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

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

  • 相关阅读:
    简单明了的带你理解springboot原理和三大核心注解
    Spring Boot(一):入门篇
    【Mysql优化】聚簇索引与非聚簇索引概念
    Mysql索引原理与优化
    Mysql全文索引的使用
    索引的优缺点,如何创建索引
    184 01 Android 零基础入门 03 Java常用工具类03 Java字符串 02 String类 04 例:字符串与byte(即:字节)数组间的相互转换
    183 01 Android 零基础入门 03 Java常用工具类03 Java字符串 02 String类 03 String常用方法(下)
    182 01 Android 零基础入门 03 Java常用工具类03 Java字符串 02 String类 02 String常用方法(上)
    181 01 Android 零基础入门 03 Java常用工具类03 Java字符串 02 String类 01 String常用方法简介
  • 原文地址:https://www.cnblogs.com/samwu/p/6473071.html
Copyright © 2011-2022 走看看