大家好,我是鱼樱!!!
关注公众号【鱼樱AI实验室】
持续分享更多前端和AI辅助前端编码新知识~~
写点笔记写点生活~写点经验。
在当前环境下,纯前端开发者可以通过技术深化、横向扩展、切入新兴领域以及产品化思维找到突破口。
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1. 组件概述
词云(Word Cloud)是一种视觉上的文字展示方式,常用于直观地表现文本数据中词语的频率或重要性。本组件基于Vue3和TypeScript实现,使用d3-cloud库作为布局引擎,具有高度可定制性和交互性,可应用于各种数据可视化场景。
2. 技术栈
- 框架: Vue3 + TypeScript
- 布局引擎: d3-cloud
- 颜色比例尺: d3-scale
- 色彩方案: d3-scale-chromatic
- 构建工具: Vite
3. 词云组件
该项目包含一个功能完整的词云组件,支持以下特性:
- 根据数据权重自动调整文字大小
- 支持自定义颜色方案或使用D3内置色彩
- 可配置文字旋转角度
- 响应式设计,自适应容器大小
- 交互反馈,鼠标悬停效果
效果图


参考代码
词云组件
html
<template>
<div class="word-cloud-container" ref="cloudContainer">
<svg :width="width" :height="height" class="word-cloud-svg">
<g :transform="`translate(${width / 2}, ${height / 2})`">
<text
v-for="(word, index) in words"
:key="index"
:font-size="word.size + 'px'"
:fill="word.color"
:transform="`translate(${word.x}, ${word.y}) rotate(${word.rotate})`"
:style="{ fontFamily }"
text-anchor="middle"
dominant-baseline="middle"
class="word-cloud-text"
>
{{ word.text }}
</text>
</g>
</svg>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch, computed } from "vue";
import cloud from "d3-cloud";
import { scaleOrdinal } from "d3-scale";
import * as d3ScaleChromatic from "d3-scale-chromatic";
// 导入自定义类型
import type { Word } from "../types/custom-types";
// 词云词语类型
interface CloudWord {
text: string;
size: number;
value: number;
x?: number;
y?: number;
rotate?: number;
color?: string;
}
// 定义组件属性
const props = defineProps({
// 词云数据数组,每项应包含text和value属性
data: {
type: Array as () => Array<{ text: string; value: number }>,
required: true,
default: () => [],
},
// 词云宽度
width: {
type: Number,
default: 500,
},
// 词云高度
height: {
type: Number,
default: 400,
},
// 字体
fontFamily: {
type: String,
default: "Arial, sans-serif",
},
// 最小字号
minFontSize: {
type: Number,
default: 12,
},
// 最大字号
maxFontSize: {
type: Number,
default: 60,
},
// 旋转角度数组
rotations: {
type: Array as () => number[],
default: () => [0],
},
// 自定义颜色数组
colors: {
type: Array as () => string[],
default: null,
},
// 颜色主题,从d3-scale-chromatic中选择
colorScheme: {
type: String,
default: "schemeCategory10",
validator: (value: string) => {
return Object.keys(d3ScaleChromatic).includes(value);
},
},
// 是否随机布局
random: {
type: Boolean,
default: true,
},
// 布局迭代次数
iterations: {
type: Number,
default: 100,
},
});
// 内部状态
const words = ref<CloudWord[]>([]);
const cloudContainer = ref<HTMLElement | null>(null);
// 计算颜色比例尺
const colorScale = computed(() => {
if (props.colors && props.colors.length > 0) {
return scaleOrdinal<string>().range(props.colors);
} else {
// 使用d3提供的颜色主题
const colorRange = (d3ScaleChromatic as any)[props.colorScheme];
return scaleOrdinal<string>().range(
typeof colorRange === "function" ? colorRange(10) : colorRange
);
}
});
// 生成词云
const generateWordCloud = () => {
if (!props.data || props.data.length === 0) return;
// 查找最大和最小值,用于缩放字体大小
const maxValue = Math.max(...props.data.map((d) => d.value));
const minValue = Math.min(...props.data.map((d) => d.value));
// 创建词云布局
const layout = cloud()
.size([props.width, props.height])
.words(props.data.map((d) => ({ text: d.text, size: 0, value: d.value })))
.padding(5)
.rotate(
() => props.rotations[Math.floor(Math.random() * props.rotations.length)]
)
.fontSize((d: Word) => {
// 线性缩放字体大小
if (maxValue === minValue) return props.minFontSize;
const size =
props.minFontSize +
(((d.value as number) - minValue) / (maxValue - minValue)) *
(props.maxFontSize - props.minFontSize);
return size;
})
.random(props.random ? Math.random : () => 0.5)
.spiral("archimedean")
.on("end", (cloudWords: Word[]) => {
words.value = cloudWords.map((w, i: number) => ({
...w,
text: w.text,
size: w.size,
value: w.value as number,
x: w.x,
y: w.y,
rotate: w.rotate,
color: colorScale.value(i.toString()),
}));
});
// 启动布局
layout.start();
};
// 监听数据变化,重新生成词云
watch(
() => props.data,
() => {
generateWordCloud();
},
{ deep: true }
);
// 监听尺寸变化,重新生成词云
watch([() => props.width, () => props.height], () => {
generateWordCloud();
});
// 组件挂载时生成词云
onMounted(() => {
generateWordCloud();
});
// 暴露方法,允许外部手动刷新词云
defineExpose({
refresh: generateWordCloud,
});
</script>
<script lang="ts">
export default {
name: "WordCloud",
};
</script>
<style scoped>
.word-cloud-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.word-cloud-svg {
display: block;
margin: 0 auto;
}
.word-cloud-text {
cursor: pointer;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.word-cloud-text:hover {
opacity: 0.7;
transform: scale(1.1);
}
</style>
词云组件使用
html
<template>
<div class="word-cloud-demo">
<h1 class="title">词云组件示例</h1>
<div class="control-panel">
<h3>词云配置</h3>
<div class="control-group">
<label>颜色方案:</label>
<select v-model="colorScheme">
<option value="schemeCategory10">默认配色</option>
<option value="schemeAccent">亮色配色</option>
<option value="schemePaired">成对配色</option>
<option value="schemeSet3">柔和配色</option>
<option value="schemeDark2">深色配色</option>
</select>
</div>
<div class="control-group">
<label>字体旋转:</label>
<select v-model="rotationMode">
<option value="horizontal">水平文字</option>
<option value="mixed">混合旋转</option>
<option value="vertical">垂直文字</option>
</select>
</div>
<div class="control-group">
<label>最大字号:</label>
<input type="range" min="20" max="100" v-model="maxFontSize" />
<span>{{ maxFontSize }}px</span>
</div>
<div class="control-group">
<label>最小字号:</label>
<input type="range" min="8" max="30" v-model="minFontSize" />
<span>{{ minFontSize }}px</span>
</div>
<div class="control-group">
<label>自定义词汇:</label>
<textarea
v-model="customWordsText"
placeholder="输入格式:词汇1:权重1,词汇2:权重2,..."
></textarea>
</div>
<div class="control-group">
<button @click="updateWords">更新词云</button>
<button @click="randomizeWords">随机数据</button>
</div>
</div>
<div class="word-cloud-wrapper">
<WordCloud
:data="wordData"
:width="800"
:height="500"
:min-font-size="minFontSize"
:max-font-size="maxFontSize"
:rotations="rotationsMap[rotationMode]"
:color-scheme="colorScheme"
ref="wordCloudRef"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import WordCloud from "../components/WordCloud.vue";
// 旋转模式映射
const rotationsMap: { [key: string]: number[] } = {
horizontal: [0],
mixed: [-60, -30, 0, 30, 60],
vertical: [90, -90],
};
// 词云配置
const colorScheme = ref("schemeCategory10");
const rotationMode = ref("horizontal");
const maxFontSize = ref(60);
const minFontSize = ref(12);
const customWordsText = ref("");
const wordCloudRef = ref<any>(null);
// 示例数据
const defaultWords = [
{ text: "词云", value: 100 },
{ text: "Vue3", value: 85 },
{ text: "TypeScript", value: 80 },
{ text: "可视化", value: 75 },
{ text: "前端开发", value: 70 },
{ text: "组件库", value: 65 },
{ text: "JavaScript", value: 60 },
{ text: "数据展示", value: 55 },
{ text: "D3.js", value: 50 },
{ text: "交互设计", value: 45 },
{ text: "CSS", value: 40 },
{ text: "HTML", value: 35 },
{ text: "响应式", value: 30 },
{ text: "组合式API", value: 25 },
{ text: "状态管理", value: 20 },
{ text: "动画效果", value: 15 },
];
// 当前词云数据
const wordData = ref<Array<{ text: string; value: number }>>(defaultWords);
// 解析自定义词汇文本
const parseCustomWords = () => {
if (!customWordsText.value.trim()) return [];
try {
return customWordsText.value
.split(",")
.map((item) => {
const [text, valueStr] = item.split(":");
const value = parseInt(valueStr, 10);
if (text && !isNaN(value)) {
return { text: text.trim(), value };
}
return null;
})
.filter((item) => item !== null) as { text: string; value: number }[];
} catch (e) {
console.error("解析自定义词汇失败", e);
return [];
}
};
// 更新词云数据
const updateWords = () => {
const customWords = parseCustomWords();
wordData.value = customWords.length > 0 ? customWords : defaultWords;
// 手动刷新词云
setTimeout(() => {
wordCloudRef.value?.refresh();
}, 100);
};
// 生成随机数据
const randomizeWords = () => {
wordData.value = defaultWords.map((word) => ({
text: word.text,
value: Math.floor(Math.random() * 100) + 10,
}));
};
// 组件挂载时初始化
onMounted(() => {
updateWords();
});
</script>
<style scoped>
.word-cloud-demo {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.title {
text-align: center;
margin-bottom: 30px;
color: #333;
}
.control-panel {
background-color: #f5f5f5;
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.control-group {
margin-bottom: 15px;
display: flex;
align-items: center;
}
.control-group label {
min-width: 100px;
margin-right: 10px;
font-weight: bold;
}
.control-group select,
.control-group input {
padding: 8px;
border-radius: 4px;
border: 1px solid #ddd;
flex: 1;
max-width: 300px;
}
.control-group textarea {
width: 100%;
height: 80px;
padding: 8px;
border-radius: 4px;
border: 1px solid #ddd;
margin-top: 5px;
}
.control-group button {
padding: 8px 16px;
background-color: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
transition: background-color 0.3s;
}
.control-group button:hover {
background-color: #2980b9;
}
.word-cloud-wrapper {
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
min-height: 500px;
display: flex;
justify-content: center;
align-items: center;
}
</style>
ts
// 词云词语基本类型
export interface Word {
text: string;
size: number;
value?: number;
x?: number;
y?: number;
rotate?: number;
[key: string]: any;
}
// 扩展的词云单词类型
export interface CloudWord extends Word {
value: number;
color?: string;
} // 词云词语基本类型
export interface Word {
text: string;
size: number;
value?: number;
x?: number;
y?: number;
rotate?: number;
[key: string]: any;
}
// 扩展的词云单词类型
export interface CloudWord extends Word {
value: number;
color?: string;
}
ts
declare module 'd3-cloud' {
interface Word {
text: string;
size: number;
value?: number;
x?: number;
y?: number;
rotate?: number;
[key: string]: any;
}
interface Cloud {
size: (size: [number, number]) => Cloud;
words: (words: Word[]) => Cloud;
padding: (padding: number) => Cloud;
rotate: (rotate: ((d: Word) => number)) => Cloud;
fontSize: (fontSize: ((d: Word) => number)) => Cloud;
random: (random: (() => number)) => Cloud;
spiral: (spiral: string) => Cloud;
on: (type: string, callback: (words: Word[]) => void) => Cloud;
start: () => void;
}
export default function(): Cloud;
}
declare module 'd3-scale' {
interface ScaleOrdinal<Range> {
(value: string | number): Range;
domain: (domain: Array<string | number>) => ScaleOrdinal<Range>;
range: (range: Range[]) => ScaleOrdinal<Range>;
}
export function scaleOrdinal<Range = string>(): ScaleOrdinal<Range>;
}
declare module 'd3-scale-chromatic' {
// 定义所有颜色方案
export const schemeCategory10: string[];
export const schemeAccent: string[];
export const schemeDark2: string[];
export const schemePaired: string[];
export const schemePastel1: string[];
export const schemePastel2: string[];
export const schemeSet1: string[];
export const schemeSet2: string[];
export const schemeSet3: string[];
export const schemeTableau10: string[];
// 其他颜色方案函数
export function interpolateBlues(t: number): string;
export function interpolateGreens(t: number): string;
export function interpolateGreys(t: number): string;
export function interpolateOranges(t: number): string;
export function interpolatePurples(t: number): string;
export function interpolateReds(t: number): string;
}