"use strict"; const common_vendor = require("./common/vendor.js"); const _sfc_main = { name: "WordCloud", props: { // 词云数据 [{text: '关键词', value: 100}, ...] wordData: { type: Array, required: true, default: () => [] }, // 画布宽度 width: { type: Number, default: 300 }, // 画布高度 height: { type: Number, default: 300 }, // 文字颜色列表(分层配色) colorList: { type: Array, default: () => ["#60A5FA", "#FEC200", "#EF4444"] // 外圈、中间、中心 }, // 新增:字号配置,让组件更灵活 fontSizeConfig: { type: Object, default: () => ({ minSize: 12, // 最小字号 maxSize: 40, // 最大字号 scaleFactor: 0.1 // 缩放因子,越大字号差异越明显 }) } }, data() { return { canvasWidth: this.width, canvasHeight: this.height, ctx: null, // canvas 2d 上下文 placedWords: [] // 已放置的文字信息(用于碰撞检测) }; }, watch: { wordData: { handler() { this.drawWordCloud(); }, deep: true } }, mounted() { this.initCanvas(); }, methods: { // 初始化canvas async initCanvas() { await new Promise((resolve) => setTimeout(resolve, 50)); const query = common_vendor.index.createSelectorQuery().in(this); query.select(".word-cloud-canvas").fields({ node: true, size: true }).exec(async (res) => { if (!res || !res[0] || !res[0].node) { common_vendor.index.__f__("error", "at components/WordCloud/WordCloud.vue:82", "获取canvas节点失败,请检查canvas是否正确渲染"); return; } const canvas = res[0].node; let ctx = null; try { ctx = canvas.getContext("2d"); } catch (e) { common_vendor.index.__f__("warn", "at components/WordCloud/WordCloud.vue:93", "获取2d上下文失败,尝试兼容处理", e); ctx = common_vendor.index.createCanvasContext("wordCloudCanvas", this); } if (!ctx) { common_vendor.index.__f__("error", "at components/WordCloud/WordCloud.vue:99", "无法获取canvas 2d上下文"); return; } const dpr = common_vendor.index.getSystemInfoSync().pixelRatio || 1; canvas.width = this.canvasWidth * dpr; canvas.height = this.canvasHeight * dpr; ctx.scale(dpr, dpr); this.ctx = ctx; this.drawWordCloud(); }); }, // 绘制词云核心方法 drawWordCloud() { if (!this.ctx || !this.wordData.length) return; this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); this.placedWords = []; const sortedWords = [...this.wordData].sort((a, b) => b.value - a.value); const values = sortedWords.map((item) => item.value); this.valueMax = Math.max(...values); this.valueMin = Math.min(...values); sortedWords.forEach((word, index) => { this.placeWord(word, index); }); }, // 放置单个文字(核心:碰撞检测,确保不重叠) placeWord(word, index) { const ctx = this.ctx; const maxAttempts = 150; const { minSize, maxSize, scaleFactor } = this.fontSizeConfig; let normalizedValue = 1; if (this.valueMax !== this.valueMin) { normalizedValue = (word.value - this.valueMin) / (this.valueMax - this.valueMin); } const fontSize = Math.min( minSize + (maxSize - minSize) * normalizedValue * scaleFactor, maxSize ); const rotateAngle = (Math.random() - 0.5) * 120 * Math.PI / 180; ctx.font = `${fontSize}px sans-serif`; const textWidth = ctx.measureText(word.text || word.name).width; const textHeight = fontSize * 1.05; for (let i = 0; i < maxAttempts; i++) { const x = this.canvasWidth * 0.05 + Math.random() * this.canvasWidth * 0.9; const y = this.canvasHeight * 0.05 + Math.random() * this.canvasHeight * 0.9; const isOverlap = this.checkOverlap(x, y, textWidth, textHeight, rotateAngle, 2); if (!isOverlap) { const centerX = this.canvasWidth / 2; const centerY = this.canvasHeight / 2; const distance = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2)); const maxDistance = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2)); let color; if (distance > maxDistance * 0.66) { color = this.colorList[0]; } else if (distance > maxDistance * 0.33) { color = this.colorList[1]; } else { color = this.colorList[2]; } ctx.fillStyle = color; this.drawTextAtPosition(word.text || word.name, x, y, rotateAngle, fontSize); this.placedWords.push({ x, y, width: textWidth, height: textHeight, angle: rotateAngle }); break; } } }, // 碰撞检测:检查当前文字是否与已放置的文字重叠(新增间距容差参数) checkOverlap(x, y, width, height, angle, gap = 2) { const currentRect = this.getBoundingRect(x, y, width, height, angle, gap); for (const placed of this.placedWords) { const placedRect = this.getBoundingRect(placed.x, placed.y, placed.width, placed.height, placed.angle, gap); if (currentRect.left < placedRect.right && currentRect.right > placedRect.left && currentRect.top < placedRect.bottom && currentRect.bottom > placedRect.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 rotatedPoints = points.map((point) => ({ x: x + point.x * cos - point.y * sin, y: y + point.x * sin + point.y * cos })); const left = Math.min(...rotatedPoints.map((p) => p.x)); const right = Math.max(...rotatedPoints.map((p) => p.x)); const top = Math.min(...rotatedPoints.map((p) => p.y)); const bottom = Math.max(...rotatedPoints.map((p) => p.y)); return { left, right, top, bottom }; }, // 在指定位置绘制旋转后的文字 drawTextAtPosition(text, x, y, angle, fontSize) { 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: $data.canvasWidth + "px", b: $data.canvasHeight + "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