Files
JiaZhiQianYan/unpackage/dist/dev/mp-weixin/WordCloud.js
2026-02-06 18:01:05 +08:00

203 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
const common_vendor = require("./common/vendor.js");
const _sfc_main = {
name: "WordCloud",
props: {
// 词云数据:[{ text: '关键词', value: 100 }]
wordData: {
type: Array,
default: () => []
},
// 画布宽度
width: {
type: Number,
default: 300
},
// 画布高度
height: {
type: Number,
default: 300
},
// 颜色:外 / 中 / 内
colorList: {
type: Array,
default: () => ["#60A5FA", "#FEC200", "#EF4444"]
}
},
data() {
return {
ctx: null,
// canvas 2d 上下文
placedWords: [],
// 已放置文字的包围盒(用于碰撞检测)
centerWords: [],
// value 最大的 3 个
otherWords: []
// 其余词
};
},
watch: {
// 词云数据变化时重绘
wordData: {
deep: true,
handler() {
this.drawWordCloud();
}
}
},
mounted() {
this.initCanvas();
},
methods: {
// 初始化 canvas
async initCanvas() {
await new Promise((r) => setTimeout(r, 50));
const query = common_vendor.index.createSelectorQuery().in(this);
query.select(".word-cloud-canvas").fields({ node: true }).exec((res) => {
if (!res || !res[0] || !res[0].node)
return;
const canvas = res[0].node;
const ctx = canvas.getContext("2d");
const dpr = common_vendor.index.getSystemInfoSync().pixelRatio || 1;
canvas.width = this.width * dpr;
canvas.height = this.height * dpr;
ctx.scale(dpr, dpr);
this.ctx = ctx;
this.drawWordCloud();
});
},
// 绘制词云
drawWordCloud() {
if (!this.ctx || !this.wordData.length)
return;
this.ctx.clearRect(0, 0, this.width, this.height);
this.placedWords = [];
const sorted = [...this.wordData].sort((a, b) => b.value - a.value);
this.centerWords = sorted.slice(0, 3);
this.otherWords = sorted.slice(3);
this.centerWords.forEach((word, index) => {
this.placeWord(word, "center", index);
});
this.otherWords.forEach((word) => {
this.placeWord(word, "other");
});
this.$emit("rendered");
},
// 放置单个词
placeWord(word, type, index = 0) {
const ctx = this.ctx;
const text = word.text || word.name;
const maxAttempts = 200;
let fontSize = 24;
let layer = "middle";
if (type === "center") {
fontSize = 32;
layer = "center";
} else {
layer = Math.random() > 0.5 ? "middle" : "outer";
fontSize = layer === "outer" ? 18 : 24;
}
const angleLimit = Math.random() > 0.5 ? 60 * Math.PI / 180 : 30 * Math.PI / 180;
const angle = (Math.random() - 0.5) * 2 * angleLimit;
ctx.font = `bold ${fontSize}px sans-serif`;
const textWidth = ctx.measureText(text).width;
const textHeight = fontSize * 1.05;
for (let i = 0; i < maxAttempts; i++) {
let x, y;
if (layer === "center") {
const centerX = this.width / 2;
const centerY = this.height / 2;
const offsets = [
{ x: 0, y: 0 },
// 最大的,正中
{ x: -80, y: 0 },
// 左
{ x: 80, y: 0 }
// 右
];
const pos = offsets[index] || offsets[0];
x = centerX + pos.x;
y = centerY + pos.y;
} else {
x = this.width * 0.05 + Math.random() * this.width * 0.9;
y = this.height * 0.05 + Math.random() * this.height * 0.9;
}
const rect = this.getBoundingRect(
x,
y,
textWidth,
textHeight,
angle,
2
);
if (layer === "outer") {
if (rect.left < 0 || rect.right > this.width || rect.top < 0 || rect.bottom > this.height) {
continue;
}
}
if (this.checkOverlapRect(rect))
continue;
ctx.fillStyle = layer === "center" ? this.colorList[2] : layer === "middle" ? this.colorList[1] : this.colorList[0];
this.drawText(text, x, y, angle);
this.placedWords.push({ rect });
break;
}
},
// 碰撞检测AABB
checkOverlapRect(current) {
for (const item of this.placedWords) {
const r = item.rect;
if (current.left < r.right && current.right > r.left && current.top < r.bottom && current.bottom > r.top) {
return true;
}
}
return false;
},
// 计算旋转后的包围盒
getBoundingRect(x, y, width, height, angle, gap = 2) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const halfW = (width - gap) / 2;
const halfH = (height - gap) / 2;
const points = [
{ x: -halfW, y: -halfH },
{ x: halfW, y: -halfH },
{ x: halfW, y: halfH },
{ x: -halfW, y: halfH }
];
const xs = [];
const ys = [];
points.forEach((p) => {
xs.push(x + p.x * cos - p.y * sin);
ys.push(y + p.x * sin + p.y * cos);
});
return {
left: Math.min(...xs),
right: Math.max(...xs),
top: Math.min(...ys),
bottom: Math.max(...ys)
};
},
// 绘制文字
drawText(text, x, y, angle) {
const ctx = this.ctx;
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(text, 0, 0);
ctx.restore();
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $props.width + "px",
b: $props.height + "px"
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-cab45d13"]]);
exports.MiniProgramPage = MiniProgramPage;
//# sourceMappingURL=../.sourcemap/mp-weixin/WordCloud.js.map