一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情。
背景
nic最近在做的项目中,首页需要加一个“词云”组件,个人觉得这个小东西比较有趣,分享给有需要的小伙伴!接下来,直接上代码!
2D词云
效果如下:
代码如下:
<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词云
效果如下:
代码如下:
<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,如果觉得写得可以的话,请点个赞吧❤。
写作不易,「点赞」+「在看」+「转发」 谢谢支持❤
往期好文
《前端CSS高频面试题---1.CSS选择器、优先级、以及继承属性》