PlotlyChartsFen.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /*
  2. * @Author: your name
  3. * @Date: 2025-05-23 17:19:20
  4. * @LastEditTime: 2025-07-04 17:03:11
  5. * @LastEditors: bogon
  6. * @Description: In User Settings Edit
  7. * @FilePath: /downLoadServer/src/server/utils/chartsCom/PlotlyChartsFen.js
  8. */
  9. import puppeteer from "puppeteer";
  10. import fs from "fs-extra";
  11. import path from "path";
  12. import FormData from "form-data";
  13. import { colorSchemes } from "../colors.js";
  14. import axios from "axios";
  15. /**
  16. * 生成折线图并上传
  17. * @param {Object} data - 图表数据
  18. * @returns {Promise<String>} - 返回图片URL
  19. */
  20. export const generatePlotlyChartsFen = async (
  21. data,
  22. bucketName,
  23. objectName,
  24. fieldEngineCode
  25. ) => {
  26. try {
  27. // 创建临时目录
  28. const tempDir = path.join(process.cwd(), "images");
  29. await fs.ensureDir(tempDir);
  30. const tempFilePath = path.join(
  31. tempDir,
  32. `temp_heatmap_chart_${Date.now()}.jpeg`
  33. );
  34. // 获取 plotly.js 的绝对路径
  35. const plotlyPath = path.join(
  36. process.cwd(),
  37. "src",
  38. "public",
  39. "js",
  40. "plotly-latest.min.js"
  41. );
  42. const plotlyContent = await fs.readFile(plotlyPath, "utf-8");
  43. // 创建浏览器实例
  44. const browser = await puppeteer.launch({
  45. headless: "new",
  46. // 根据系统改路径
  47. executablePath: `${process.env.CHROME_PATH}`, // 根据系统改路径
  48. args: ["--no-sandbox", "--disable-setuid-sandbox"],
  49. });
  50. try {
  51. const page = await browser.newPage();
  52. // 准备图表数据
  53. const sortedData = data.data.sort((a, b) => {
  54. if (
  55. a.enginCode === fieldEngineCode &&
  56. b.enginCode !== fieldEngineCode
  57. ) {
  58. return 1;
  59. }
  60. if (
  61. a.enginCode !== fieldEngineCode &&
  62. b.enginCode === fieldEngineCode
  63. ) {
  64. return -1;
  65. }
  66. return 0;
  67. });
  68. const finalData = [];
  69. let enginName = "";
  70. console.log(fieldEngineCode, "fieldEngineCode");
  71. sortedData
  72. .filter((item) => item.enginName !== "合同功率曲线")
  73. .forEach((turbine) => {
  74. const color =
  75. turbine.enginCode === fieldEngineCode ? "#406DAB" : "#D3D3D3";
  76. enginName =
  77. turbine.enginCode === fieldEngineCode ? turbine.enginName : "";
  78. const chartConfig = {
  79. x: turbine.xData,
  80. y: turbine.yData,
  81. connectgaps: false,
  82. name: turbine.enginName,
  83. mode: "lines",
  84. fill: "none",
  85. line: { color },
  86. marker: { color },
  87. };
  88. finalData.push(chartConfig);
  89. });
  90. const filterData = data.data.filter(
  91. (item) => item.enginName === "合同功率曲线"
  92. );
  93. console.log(filterData, "合同功率曲线");
  94. // 添加合同功率曲线
  95. if (filterData && filterData[0].enginName === "合同功率曲线") {
  96. finalData.push({
  97. x: filterData[0].xData,
  98. y: filterData[0].yData,
  99. mode: "lines+markers",
  100. name: "合同功率曲线",
  101. line: {
  102. color: "red",
  103. width: 1,
  104. },
  105. marker: { color: "red", size: 4 },
  106. });
  107. }
  108. // 准备布局配置
  109. const layout = {
  110. title: {
  111. text: `机组 ${enginName}风速功率曲线分析`,
  112. font: {
  113. size: 16,
  114. weight: "bold",
  115. },
  116. },
  117. xaxis: {
  118. title: {
  119. text: "风速(m/s)" || "x轴",
  120. },
  121. gridcolor: "rgb(255,255,255)",
  122. tickcolor: "rgb(255,255,255)",
  123. backgroundcolor: "#e5ecf6",
  124. },
  125. yaxis: {
  126. title: { text: "功率(kW)" || "Y轴" },
  127. gridcolor: "rgb(255,255,255)",
  128. tickcolor: "rgb(255,255,255)",
  129. backgroundcolor: "#e5ecf6",
  130. },
  131. margin: {
  132. l: 50,
  133. r: 50,
  134. t: 50,
  135. b: 50,
  136. },
  137. autosize: true,
  138. plot_bgcolor: "#e5ecf6",
  139. gridcolor: "#fff",
  140. bgcolor: "#e5ecf6",
  141. legend: {
  142. orientation: "h",
  143. xanchor: "center",
  144. x: 0.5,
  145. y: -0.2,
  146. },
  147. };
  148. // 创建HTML内容
  149. const htmlContent = `
  150. <!DOCTYPE html>
  151. <html>
  152. <head>
  153. <script>${plotlyContent}</script>
  154. <style>
  155. body { margin: 0; }
  156. #chart { width: 800px; height: 600px; }
  157. </style>
  158. </head>
  159. <body>
  160. <div id="chart"></div>
  161. <script>
  162. window.onload = function() {
  163. const data = ${JSON.stringify(finalData)};
  164. const layout = ${JSON.stringify(layout)};
  165. Plotly.newPlot('chart', data, layout, {
  166. responsive: true
  167. }).then(() => {
  168. window.chartRendered = true;
  169. });
  170. };
  171. </script>
  172. </body>
  173. </html>
  174. `;
  175. // 设置页面内容
  176. await page.setContent(htmlContent, {
  177. waitUntil: "networkidle0",
  178. });
  179. // 等待图表渲染完成
  180. await page.waitForFunction(() => window.chartRendered === true, {
  181. timeout: 60000,
  182. });
  183. // 截图并保存到临时文件
  184. // 截图并保存到临时文件
  185. const chartElement = await page.$("#chart");
  186. await chartElement.screenshot({
  187. path: tempFilePath,
  188. type: "jpeg",
  189. });
  190. // 上传图片到服务器
  191. const formData = new FormData();
  192. formData.append("file", fs.createReadStream(tempFilePath));
  193. // return formData;
  194. // 发送上传请求
  195. const response = await axios.post(
  196. `${process.env.API_BASE_URL}/examples/upload`,
  197. { filePath: tempFilePath, bucketName, objectName }
  198. );
  199. return response?.data?.url;
  200. } finally {
  201. await browser.close();
  202. }
  203. } catch (error) {
  204. console.error("生成折线图失败:", error);
  205. throw error;
  206. }
  207. };