"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