1.31 财务分析,财务数据模块完善,产业链桑基图展示
This commit is contained in:
207
unpackage/dist/dev/mp-weixin/WordCloud.js
vendored
Normal file
207
unpackage/dist/dev/mp-weixin/WordCloud.js
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
"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
|
||||
Reference in New Issue
Block a user