Radar.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import puppeteer from "puppeteer";
  2. import fs from "fs-extra";
  3. import path from "path";
  4. import FormData from "form-data";
  5. import { colorSchemes } from "../colors.js";
  6. import axios from "axios";
  7. // 获取 plotly.js 的绝对路径
  8. const plotlyPath = path.join(
  9. process.cwd(),
  10. "src",
  11. "public",
  12. "js",
  13. "echarts.min.js"
  14. );
  15. const plotlyContent = await fs.readFile(plotlyPath, "utf-8");
  16. // HTML 模板
  17. const getHtmlContent = () => `
  18. <!DOCTYPE html>
  19. <html lang="en">
  20. <head>
  21. <meta charset="UTF-8" />
  22. <title>Radar Chart</title>
  23. <script>${plotlyContent}</script>
  24. <style>
  25. html, body { margin: 0; padding: 0; width: 600px; height: 600px; }
  26. #chart { width: 100%; height: 100%; }
  27. </style>
  28. </head>
  29. <body>
  30. <div id="chart"></div>
  31. <script>
  32. window.renderChart = function (chartData, itemCsvData) {
  33. function calcValues(data) {
  34. const matrix = data.map((item) => [
  35. item.TurbinePowerRate,
  36. item.TurbineRunRate,
  37. item.WindSpeedAvr,
  38. item.Thi,
  39. item.Ws,
  40. ]);
  41. const max = matrix[0].map((_, i) => Math.max(...matrix.map(row => row[i])));
  42. const min = matrix[0].map((_, i) => Math.min(...matrix.map(row => row[i])));
  43. const median = matrix[0].map((_, i) => {
  44. const sorted = matrix.map(row => row[i]).sort((a, b) => a - b);
  45. const mid = Math.floor(sorted.length / 2);
  46. return sorted.length % 2 === 0
  47. ? (sorted[mid - 1] + sorted[mid]) / 2
  48. : sorted[mid];
  49. });
  50. return { max, min, median };
  51. }
  52. const { max, min, median } = calcValues(itemCsvData);
  53. const values = [
  54. chartData.TurbinePowerRate,
  55. chartData.TurbineRunRate,
  56. chartData.WindSpeedAvr,
  57. chartData.Thi,
  58. chartData.Ws,
  59. ];
  60. const indicators = [
  61. { name: "风机能量利用率", max: max[0], min: min[0] },
  62. { name: "风机可利用率", max: max[1], min: min[1] },
  63. { name: "平均风速", max: max[2], min: min[2] },
  64. { name: "等效利用小时", max: max[3], min: min[3] },
  65. { name: "功率曲线一致性系数", max: max[4], min: min[4] },
  66. ];
  67. const chart = echarts.init(document.getElementById("chart"));
  68. chart.setOption({
  69. title: {
  70. text: chartData.wind_turbine_name + "机组指标",
  71. left: "center",
  72. },
  73. radar: {
  74. indicator: indicators,
  75. center: ["50%", "50%"],
  76. radius: "60%",
  77. },
  78. series: [
  79. {
  80. type: "radar",
  81. data: [
  82. {
  83. value: values,
  84. name: chartData.wind_turbine_name,
  85. areaStyle: { color: "rgba(99,110,252,0.3)" },
  86. },
  87. {
  88. value: median,
  89. name: "中位值",
  90. lineStyle: {
  91. type: "dashed",
  92. color: "#f39c12",
  93. },
  94. },
  95. ],
  96. },
  97. ],
  98. });
  99. };
  100. </script>
  101. </body>
  102. </html>
  103. `;
  104. export const getRadarCharts = async (
  105. chartData,
  106. itemCsvData,
  107. bucketName,
  108. objectName
  109. ) => {
  110. const browser = await puppeteer.launch();
  111. const page = await browser.newPage();
  112. // 创建临时目录
  113. const tempDir = path.join(process.cwd(), "images");
  114. await fs.ensureDir(tempDir);
  115. const tempFilePath = path.join(
  116. tempDir,
  117. `temp_scatter_chart_${Date.now()}.png`
  118. );
  119. await page.setContent(getHtmlContent(), { waitUntil: "load" });
  120. // 等待 renderChart 被定义
  121. await page.waitForFunction(() => typeof window.renderChart === "function");
  122. // 调用渲染函数
  123. await page.evaluate(
  124. (chartData, itemCsvData) => {
  125. window.renderChart(chartData, itemCsvData);
  126. },
  127. chartData,
  128. itemCsvData
  129. );
  130. // 再等待图表渲染完成(给 ECharts 时间)
  131. (await page.waitForTimeout)
  132. ? page.waitForTimeout(1000)
  133. : new Promise((res) => setTimeout(res, 1000));
  134. // 上传逻辑
  135. // 截图并保存到临时文件
  136. const chartElement = await page.$("#chart");
  137. await chartElement.screenshot({
  138. path: tempFilePath,
  139. type: "png",
  140. });
  141. try {
  142. const newUrl = objectName.substring(0, objectName.lastIndexOf("/"));
  143. // 上传图片到服务器
  144. const formData = new FormData();
  145. formData.append("file", fs.createReadStream(tempFilePath));
  146. // 发送上传请求
  147. const response = await axios.post(
  148. `${process.env.API_BASE_URL}/examples/upload`,
  149. {
  150. filePath: tempFilePath,
  151. bucketName,
  152. objectName: newUrl + "/" + chartData.wind_turbine_name + ".jpg",
  153. }
  154. );
  155. return response?.data?.url;
  156. } catch (error) {
  157. console.error("❌ 上传失败:", error.message);
  158. }
  159. };