|
@@ -0,0 +1,247 @@
|
|
|
+/*
|
|
|
+ * @Author: your name
|
|
|
+ * @Date: 2025-04-28 14:15:23
|
|
|
+ * @LastEditTime: 2025-04-28 14:33:09
|
|
|
+ * @LastEditors: bogon
|
|
|
+ * @Description: In User Settings Edit
|
|
|
+ * @FilePath: /downLoadServer/src/server/utils/chartsCom/powerMarkers2DCharts.js
|
|
|
+ */
|
|
|
+import puppeteer from "puppeteer";
|
|
|
+import fs from "fs-extra";
|
|
|
+import path from "path";
|
|
|
+import FormData from "form-data";
|
|
|
+import { colorSchemes } from "../colors.js";
|
|
|
+
|
|
|
+export const generatepowerMarkers2DCharts = async (data) => {
|
|
|
+ try {
|
|
|
+ const colorsBar = colorSchemes[0].colors;
|
|
|
+ // 创建临时目录
|
|
|
+ const tempDir = path.join(process.cwd(), "images");
|
|
|
+ await fs.ensureDir(tempDir);
|
|
|
+ const tempFilePath = path.join(
|
|
|
+ tempDir,
|
|
|
+ `temp_scatter_chart_${Date.now()}.png`
|
|
|
+ );
|
|
|
+
|
|
|
+ // 获取 plotly.js 的绝对路径
|
|
|
+ const plotlyPath = path.join(
|
|
|
+ process.cwd(),
|
|
|
+ "src",
|
|
|
+ "public",
|
|
|
+ "js",
|
|
|
+ "plotly-3.0.1.min.js"
|
|
|
+ );
|
|
|
+ const plotlyContent = await fs.readFile(plotlyPath, "utf-8");
|
|
|
+
|
|
|
+ // 创建浏览器实例
|
|
|
+ const browser = await puppeteer.launch({
|
|
|
+ headless: "new",
|
|
|
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ const page = await browser.newPage();
|
|
|
+
|
|
|
+ // 提取散点数据和线数据
|
|
|
+ const scatterData = data.data.filter(
|
|
|
+ (item) => item.engineName !== "合同功率曲线"
|
|
|
+ )[0]; // 点数据
|
|
|
+
|
|
|
+ const lineData = data.data.filter(
|
|
|
+ (item) => item.engineName === "合同功率曲线"
|
|
|
+ )[0]; // 线数据
|
|
|
+
|
|
|
+ // 保存原始颜色和大小
|
|
|
+ const originalColors = [...scatterData.yData];
|
|
|
+ const originalSizes = new Array(scatterData.xData.length).fill(6); // 初始点大小
|
|
|
+
|
|
|
+ // 如果有 colorbar 数据
|
|
|
+ const uniqueTimeLabels =
|
|
|
+ scatterData.colorbar &&
|
|
|
+ scatterData.colorbar.length === scatterData.xData.length
|
|
|
+ ? [...new Set(scatterData.colorbar)] // 从 colorbar 中提取唯一的标签
|
|
|
+ : [...new Set(scatterData.color)]; // 如果没有 colorbar,使用 data.color
|
|
|
+
|
|
|
+ const ticktext = uniqueTimeLabels.map((dateStr) => dateStr); // 格式化为标签
|
|
|
+ const tickvals = uniqueTimeLabels.map((label, index) => index + 1); // 设置 tick 值
|
|
|
+ const timeMapping = uniqueTimeLabels.reduce((acc, curr, index) => {
|
|
|
+ acc[curr] = index + 1;
|
|
|
+ return acc;
|
|
|
+ }, {});
|
|
|
+
|
|
|
+ // 获取 colorbar 的最小值和最大值来计算比例值
|
|
|
+ const minValue = Math.min(...tickvals);
|
|
|
+ const maxValue = Math.max(...tickvals);
|
|
|
+
|
|
|
+ // 仅取 colorsBar 的前 4 种颜色进行渐变
|
|
|
+ const colorStops = [
|
|
|
+ colorsBar[0],
|
|
|
+ colorsBar[4],
|
|
|
+ colorsBar[8],
|
|
|
+ colorsBar[12],
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 计算渐变比例
|
|
|
+ const colors = colorStops.map((color, index) => {
|
|
|
+ const proportion = index / (colorStops.length - 1); // 计算比例值 (0, 1/3, 2/3, 1)
|
|
|
+ return [proportion, color]; // 生成颜色映射
|
|
|
+ });
|
|
|
+
|
|
|
+ // 确保至少有 2 个颜色,否则使用默认颜色
|
|
|
+ if (colors.length < 2) {
|
|
|
+ colors.push([1, colorStops[colorStops.length - 1] || "#1B2973"]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算颜色值映射
|
|
|
+ let colorValues = [];
|
|
|
+ if (scatterData.colorbar) {
|
|
|
+ colorValues = scatterData.colorbar.map((date) => timeMapping[date]);
|
|
|
+ } else {
|
|
|
+ colorValues = scatterData.color.map((date) => timeMapping[date]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 绘制散点图
|
|
|
+ const scatterTrace = {
|
|
|
+ x: scatterData.xData,
|
|
|
+ y: scatterData.yData,
|
|
|
+ mode: "markers", // 散点
|
|
|
+ type: "scattergl", // 使用散点图
|
|
|
+ text: scatterData.engineName, // 提示文本
|
|
|
+ marker: {
|
|
|
+ color: colorValues, // 使用时间数据来映射颜色
|
|
|
+ colorscale: colors
|
|
|
+ ? [...colors]
|
|
|
+ : [
|
|
|
+ [0, "#F9FDD2"],
|
|
|
+ [0.15, "#E9F6BD"],
|
|
|
+ [0.3, "#C2E3B9"],
|
|
|
+ [0.45, "#8AC8BE"],
|
|
|
+ [0.6, "#5CA8BF"],
|
|
|
+ [0.75, "#407DB3"],
|
|
|
+ [0.9, "#2E4C9A"],
|
|
|
+ [1, "#1B2973"],
|
|
|
+ ], // 默认颜色渐变
|
|
|
+ colorbar: {
|
|
|
+ title: scatterData.colorbartitle || "Color Legend", // 色标标题
|
|
|
+ tickvals: tickvals, // 设置刻度值
|
|
|
+ ticktext: ticktext, // 设置刻度文本
|
|
|
+ tickmode: "array", // 使用数组模式
|
|
|
+ tickangle: -45, // 可选:调整刻度文本的角度
|
|
|
+ },
|
|
|
+ size: 4, // 点的大小
|
|
|
+ line: {
|
|
|
+ color: "#fff", // 让边框颜色和点颜色相同
|
|
|
+ width: 0.3, // 设置边框宽度
|
|
|
+ },
|
|
|
+ },
|
|
|
+ hovertemplate:
|
|
|
+ `${data.xaixs}: %{x} <br> ` +
|
|
|
+ `${data.yaixs}: %{y} <br> ` +
|
|
|
+ `时间: %{customdata}<extra></extra>`, // 在 hover 中显示格式化后的时间
|
|
|
+ customdata: scatterData.colorbar || scatterData.color, // 将格式化后的时间存入 customdata
|
|
|
+ };
|
|
|
+
|
|
|
+ // 绘制线图
|
|
|
+ let lineTrace = {};
|
|
|
+ if (lineData) {
|
|
|
+ lineTrace = {
|
|
|
+ x: lineData.xData, // 线数据的 xData
|
|
|
+ y: lineData.yData, // 线数据的 yData
|
|
|
+ mode: "lines+markers", // 线和点同时显示
|
|
|
+ type: "scattergl", // 使用 scattergl 类型
|
|
|
+ text: lineData.engineName, // 提示文本
|
|
|
+ line: {
|
|
|
+ color: "red", // 线条颜色
|
|
|
+ },
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 图表布局
|
|
|
+ const layout = {
|
|
|
+ title: {
|
|
|
+ text: scatterData.title,
|
|
|
+ font: {
|
|
|
+ size: 16,
|
|
|
+ weight: "bold",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ xaxis: {
|
|
|
+ title: {
|
|
|
+ text: data.xaixs,
|
|
|
+ },
|
|
|
+ gridcolor: "rgb(255,255,255)", // 网格线颜色
|
|
|
+ tickcolor: "rgb(255,255,255)",
|
|
|
+ backgroundcolor: "#e5ecf6",
|
|
|
+ showbackground: true, // 显示背景
|
|
|
+ },
|
|
|
+ yaxis: {
|
|
|
+ title: { text: data.yaixs },
|
|
|
+ gridcolor: "rgb(255,255,255)", // 网格线颜色
|
|
|
+ tickcolor: "rgb(255,255,255)",
|
|
|
+ backgroundcolor: "#e5ecf6",
|
|
|
+ showbackground: true, // 显示背景
|
|
|
+ },
|
|
|
+ showlegend: false,
|
|
|
+ plot_bgcolor: "#e5ecf6",
|
|
|
+ gridcolor: "#fff", // 设置网格线颜色
|
|
|
+ };
|
|
|
+
|
|
|
+ // 准备 HTML 内容
|
|
|
+ const htmlContent = `
|
|
|
+ <!DOCTYPE html>
|
|
|
+ <html>
|
|
|
+ <head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <title>2D 散点图</title>
|
|
|
+ <script>${plotlyContent}</script>
|
|
|
+ </head>
|
|
|
+ <body>
|
|
|
+ <div id="chart" style="width: 100%; height: 600px"></div>
|
|
|
+ <script>
|
|
|
+ const traces = [${JSON.stringify(scatterTrace)}${
|
|
|
+ lineTrace ? `, ${JSON.stringify(lineTrace)}` : ""
|
|
|
+ }];
|
|
|
+ const layout = ${JSON.stringify(layout)};
|
|
|
+ Plotly.newPlot('chart', traces, layout, { responsive: true }).then(() => {
|
|
|
+ window.chartRendered = true; // 确保在图表渲染完成后设置
|
|
|
+ console.log("图表渲染完成");
|
|
|
+ }).catch((error) => {
|
|
|
+ console.error("图表渲染错误:", error); // 捕获渲染错误
|
|
|
+ });
|
|
|
+ </script>
|
|
|
+ </body>
|
|
|
+ </html>
|
|
|
+ `;
|
|
|
+
|
|
|
+ // 设置页面内容
|
|
|
+ await page.setContent(htmlContent, {
|
|
|
+ waitUntil: "networkidle0",
|
|
|
+ });
|
|
|
+
|
|
|
+ // 等待图表渲染完成,延长超时时间
|
|
|
+ await page.waitForFunction(() => window.chartRendered === true, {
|
|
|
+ timeout: 150000, // 延长到 150 秒
|
|
|
+ });
|
|
|
+
|
|
|
+ // 截图并保存到临时文件
|
|
|
+ const chartElement = await page.$("#chart");
|
|
|
+ await chartElement.screenshot({
|
|
|
+ path: tempFilePath,
|
|
|
+ type: "png",
|
|
|
+ });
|
|
|
+
|
|
|
+ // 上传图片到服务器
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append("file", fs.createReadStream(tempFilePath));
|
|
|
+ return formData;
|
|
|
+ } catch (error) {
|
|
|
+ console.error("生成2D散点图失败:", error);
|
|
|
+ throw error;
|
|
|
+ } finally {
|
|
|
+ await browser.close();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("生成2D散点图失败:", error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+};
|