2.词云-vue-wordcloud组件封装

2,540 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

背景

nic最近在做的项目中,首页需要加一个“词云”组件,个人觉得这个小东西比较有趣,分享给有需要的小伙伴!接下来,直接上代码!

微信图片_20211206113312.jpg

2D词云

效果如下:

image.png 代码如下:


<template>
  <div>
    <div id="word_cloud_view_id" style="width: 500px; height: 250px"></div>
  </div>
</template>
<script>
import * as echarts from "echarts";
import "echarts-wordcloud";
export default {
  name: "word_cloud_index",
  data() {
    return {
      word_cloud_view: null,
      word_cloud_options: {},
      word_list: [
        {
          name: "十九大精神",
          id: "123",
          value: 15000,
        },
        {
          name: "两学一做",
          id: "123",
          value: 10081,
        },
        {
          name: "中华民族",
          id: "123",
          value: 9386,
        },
        {
          name: "马克思主义",
          id: "123",
          value: 7500,
        },
        {
          name: "民族复兴",
          id: "123",
          value: 7500,
        },
        {
          name: "社会主义制度",
          id: "123",
          value: 6500,
        },
        {
          name: "国防白皮书",
          id: "123",
          value: 6500,
        },
        {
          name: "创新",
          id: "123",
          value: 6000,
        },
        {
          name: "民主革命",
          id: "123",
          value: 4500,
        },
        {
          name: "文化强国",
          id: "123",
          value: 3800,
        },
        {
          name: "国家主权",
          id: "123",
          value: 3000,
        },
        {
          name: "武装颠覆",
          id: "123",
          value: 2500,
        },
        {
          name: "领土完整",
          id: "123",
          value: 2300,
        },
        {
          name: "安全",
          id: "123",
          value: 2000,
        },
        {
          name: "从严治党",
          id: "123",
          value: 1900,
        },
        {
          name: "现代化经济体系",
          id: "123",
          value: 1800,
        },
        {
          name: "国防动员",
          id: "123",
          value: 1700,
        },
        {
          name: "信息化战争",
          id: "123",
          value: 1600,
        },
        {
          name: "局部战争",
          id: "123",
          value: 1500,
        },
        {
          name: "教育",
          id: "123",
          value: 1200,
        },
        {
          name: "职业教育",
          id: "123",
          value: 1100,
        },
        {
          name: "兵法",
          id: "123",
          value: 900,
        },
        {
          name: "一国两制",
          id: "123",
          value: 800,
        },
        {
          name: "特色社会主义思想",
          id: "123",
          value: 700,
        },
      ],
    };
  },
  mounted() {
    this.draw_word_cloud_view()
    this.init_view_data();
  },
  methods: {
    // 初始化控件
    draw_word_cloud_view() {
      if (this.word_cloud_view) {
        this.word_cloud_view.dispose();
      }
      this.word_cloud_view = document.getElementById('word_cloud_view_id') && echarts.init(document.getElementById('word_cloud_view_id'));
      this.word_cloud_view && this.word_cloud_view.setOption(this.word_cloud_options);
    },
    // 初始化参数
    init_view_data() {
      let word_cloud_series = [
        {
          type: "wordCloud",
          shape: "circle",
          left: "center",
          clickable: true,
          top: "center",
          width: "100%",
          height: "100%",
          right: null,
          bottom: null,
          sizeRange: [12, 50], // 字号范围
          rotationRange: [0, 0], // 云中文字的角度
          rotationStep: 0, // 渲染的梯度
          gridSize: 50, // 词的间距
          drawOutOfBound: false, // 是否允许词云在边界外渲染
          layoutAnimation: true,
          textStyle: {
            fontFamily: "sans-serif",
            fontWeight: "bold",
            color:  () => {
              return (
                "rgb(" +
                [
                  Math.round(Math.random() * 255),
                  Math.round(Math.random() * 255),
                  Math.round(Math.random() * 255),
                ].join(",") +
                ")"
              );
            },
          },
          // 鼠标进入样式
          emphasis: {
            focus: "self",
            textStyle: {
              shadowBlur: 5,
              shadowColor: "#F5F5F5",
            },
          },
          data: this.word_list, // 数据
        },
      ];
      this.word_cloud_options = {
        series: word_cloud_series,
      };
      // 设置参数
      this.word_cloud_view.setOption(this.word_cloud_options);
      // 绑定点击事件
      this.word_cloud_view.on("click", (params) => {
        this.getItemInfo(params.data.id);
      });
    },
    // 点击词云项
    getItemInfo(id) {
        console.log("---", id);
    }
  },
};
</script>
<style>
div{
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>

3D词云

效果如下:

je0tz-900kg.gif

代码如下:

<template>
  <div class="container">
    <svg
      style="background-color: white; border-radius: 50%;"
      :width="width"
      :height="height"
      ref="test"
      @mousemove="listener($event)"
    >
      <a
        class="fontA"
        :href="tag.href"
        v-for="(tag, index) in tags"
        :key="tag.id"
        @mouseenter="mouseenter($event)"
        @mouseleave="mouseleave($event)"
      >
        <text
          :x="tag.x"
          :y="tag.y"
          :fill="colors[index]"
          :font-size="tag.size"
          :fill-opacity="(400 + tag.z) / 600"
        >
          {{ tag.label }}
        </text>
      </a>
    </svg>
  </div>
</template>
<script>
export default {
  name: "word-cloud",
  //数据,宽,高,半径,半径一般位宽高的一半。
  data() {
    return {
      data: [
        {label: "十九大精神", value: "111", weight: 288},
        {label: "两学一做", value: "111", weight: 11},
        {label: "中华民族", value: "111", weight: 22},
        {label: "马克思主义", value: "111", weight: 33},
        {label: "民族复兴", value: "111", weight: 44},
        {label: "社会主义制度", value: "111", weight: 55},
        {label: "国防白皮书", value: "111", weight: 66},
        {label: "民主革命", value: "111", weight: 77},
        {label: "文化强国", value: "111", weight: 88},
        {label: "国家主权", value: "111", weight: 99},
        {label: "武装颠覆", value: "111", weight: 56},
        {label: "领土完整", value: "111", weight: 66},
        {label: "安全", value: "111", weight: 77},
        {label: "从严治党", value: "111", weight: 1},
        {label: "现代化经济体系", value: "111", weight: 122},
        {label: "国防动员", value: "111", weight: 33},
        {label: "信息化战争", value: "111", weight: 44},
        {label: "局部战争", value: "111", weight: 55},
        {label: "教育", value: "111", weight: 66},
        {label: "兵法", value: "111", weight: 77},
        {label: "一国两制", value: "111", weight: 88},
        {label: "特色社会主义思想", value: "111", weight: 99},
      ],
      width: 700, //svg宽度
      height: 700, //svg高度
      tagsNum: 0, //标签数量
      RADIUS: 250, //球的半径
      speedX: Math.PI / 360 / 1.5, //球一帧绕x轴旋转的角度
      speedY: Math.PI / 360 / 1.5, //球-帧绕y轴旋转的角度
      tags: [],
      timer: null,
      minSize: 14,
      maxSize: 44,
      colors: [], //存储颜色
    };
  },
  computed: {
    CX() {
      //球心x坐标
      return this.width / 2;
    },
    CY() {
      //球心y坐标
      return this.height / 2;
    },
  },
  created() {
    //初始化标签位置
    this.changeColors();
    let tags = [];
    this.tagsNum = this.data.length;
    for (let i = 0; i < this.data.length; i++) {
      let tag = {};
      let k = -1 + (2 * (i + 1) - 1) / this.tagsNum;
      let a = Math.acos(k);
      let b = a * Math.sqrt(this.tagsNum * Math.PI); //计算标签相对于球心的角度

      tag.label = this.data[i].label;
      tag.value = this.data[i].value;
      let size = this.minSize + parseInt((this.data[i].weight / 100) * 20)
      tag.size = size > this.maxSize ? this.maxSize : size;
      // tag.href = "";

      tag.x = this.CX + this.RADIUS * Math.sin(a) * Math.cos(b); //根据标签角度求出标签的x,y,z坐标
      tag.y = this.CY + this.RADIUS * Math.sin(a) * Math.sin(b);
      tag.z = this.RADIUS * Math.cos(a);
      tags.push(tag);
      console.log(tag);
    }
    this.tags = tags; 
  },
  
  mounted() {
    this.timer = setInterval(() => {
      this.rotateX(this.speedX);
      this.rotateY(this.speedY);
    }, 17);
  },
  methods: {
    // 纵向
    rotateX(angleX) {
      var cos = Math.cos(angleX);
      var sin = Math.sin(angleX);
      for (let tag of this.tags) {
        var y1 = (tag.y - this.CY) * cos - tag.z * sin + this.CY;
        var z1 = tag.z * cos + (tag.y - this.CY) * sin;
        tag.y = y1;
        tag.z = z1;
      }
    },

    // 横向
    rotateY(angleY) {
      var cos = Math.cos(angleY);
      var sin = Math.sin(angleY);
      for (let tag of this.tags) {
        var x1 = (tag.x - this.CX) * cos - tag.z * sin + this.CX;
        var z1 = tag.z * cos + (tag.x - this.CX) * sin;
        tag.x = x1;
        tag.z = z1;
      }
    },

    // 监听鼠标方向
    listener(e) {
      var x = e.clientX - this.CX;
      var y = e.clientY - this.CY;
      if (x * 0.0001 > 0 && y * 0.0001 > 0) {
        this.speedX = -Math.min(this.RADIUS * 0.00002, x * 0.0002);
        this.speedY = -Math.min(this.RADIUS * 0.00002, y * 0.0002);
      } else if (x * 0.0001 < 0 && y * 0.0001 < 0) {
        this.speedX = -Math.max(-this.RADIUS * 0.00002, x * 0.0002);
        this.speedY = -Math.max(-this.RADIUS * 0.00002, y * 0.0002);
      } else {
        this.speedX =
          x * 0.0001 > 0
            ? Math.min(this.RADIUS * 0.00002, x * 0.0001)
            : Math.max(-this.RADIUS * 0.00002, x * 0.0001);
        this.speedY =
          y * 0.0001 > 0
            ? Math.min(this.RADIUS * 0.00002, y * 0.0001)
            : Math.max(-this.RADIUS * 0.00002, y * 0.0001);
      }
    },

    // 鼠标进入文字
    mouseenter(e) {
      // 修改透明度
      let doms = document.getElementsByClassName('fontA');
      for(let i = 0;i<doms.length;i++) {
        doms[i].childNodes[0].style.fillOpacity = '0.3';
      }
      e.target.childNodes[0].style.fillOpacity = 1;

      // 停止动画
      clearInterval(this.timer);
      this.timer = null;
    },

    // 鼠标离开文字
    mouseleave() {
      // 修改透明度
      let doms = document.getElementsByClassName('fontA');
      for(let i = 0;i<doms.length;i++) {
        doms[i].childNodes[0].style.fillOpacity = '';
      }

      // 开始动画
      if (this.timer) {
        clearInterval(this.timer);
        this.timer = null;
      }
      this.timer = setInterval(() => {
        this.rotateX(this.speedX);
        this.rotateY(this.speedY);
      }, 17);
    },

    // 颜色
    changeColors() {
      //随机变色
      for (var i = 0; i < 22; i++) {
        var r = Math.floor(Math.random() * 256);
        var g = Math.floor(Math.random() * 256);
        var b = Math.floor(Math.random() * 256);
        this.colors[i] = "rgb(" + r + "," + g + "," + b + ")";
      }
    },
  }
};
</script>


<style lang="scss" scoped>
.container {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
}
.fontA {
  font-weight: bold;
  font-family: Apple LiGothic Medium;
}
</style>

实现方法

上边两个简单的小例子展示了2D和3D的效果。 2D是利用echart内置的对象进行开发,而3D则是根据svg特性进行开发,3D对比2D来讲更加酷炫,可扩展性更加高,小伙伴们可以在此基础上改进哟!

感谢

谢谢你读完本篇文章,希望对你能有所帮助,如有问题欢迎各位指正。

我是Nicnic,如果觉得写得可以的话,请点个赞吧❤。

写作不易,「点赞」+「在看」+「转发」 谢谢支持❤

往期好文

《Javascript高频手写题1.0》

《Vue中生成图片验证码的小组件》

《前端JS高频面试题---1.发布-订阅模式》

《前端JS高频面试题---2.单例模式》

《前端JS高频面试题---3.代理模式》

《前端JS高频面试题---4.策略模式》

《前端CSS高频面试题---1.CSS选择器、优先级、以及继承属性》

《前端CSS高频面试题---2.em/px/rem/vh/vw的区别》

《前端CSS高频面试题---3.如何实现两栏布局,右侧自适应?三栏布局中间自适应呢?》