Files

208 lines
7.2 KiB
JavaScript
Raw Permalink 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,
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