BarChart.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import puppeteer from "puppeteer";
  2. import fs from "fs-extra";
  3. import path from "path";
  4. import FormData from "form-data";
  5. /**
  6. * 生成柱状图并上传
  7. * @param {Object} data - 图表数据
  8. * @returns {Promise<String>} - 返回图片URL
  9. */
  10. export const generateBarChart = async (data) => {
  11. try {
  12. console.log("开始生成图表...");
  13. console.log("数据:", data);
  14. // 创建临时目录
  15. const tempDir = path.join(process.cwd(), "images");
  16. await fs.ensureDir(tempDir);
  17. const tempFilePath = path.join(tempDir, `temp_chart_${Date.now()}.png`);
  18. // 获取 plotly.js 的绝对路径
  19. const plotlyPath = path.join(
  20. process.cwd(),
  21. "src",
  22. "public",
  23. "js",
  24. "plotly-latest.min.js"
  25. );
  26. const plotlyContent = await fs.readFile(plotlyPath, "utf-8");
  27. // 创建浏览器实例
  28. const browser = await puppeteer.launch({
  29. headless: "new",
  30. args: ["--no-sandbox", "--disable-setuid-sandbox"],
  31. });
  32. try {
  33. const page = await browser.newPage();
  34. // 准备图表数据
  35. const chartDataset = data.data[0];
  36. const trace = {
  37. x: chartDataset.xData,
  38. y: chartDataset.yData,
  39. type: "bar",
  40. marker: {
  41. color: "#588CF0",
  42. },
  43. line: {
  44. color: "#588CF0",
  45. },
  46. name: chartDataset.title || "数据",
  47. hovertemplate: `${data.xaixs}: %{x} <br> ${data.yaixs}: %{y} <br>`,
  48. };
  49. // 准备布局配置
  50. const layout = {
  51. title: {
  52. text: chartDataset.title,
  53. font: {
  54. size: 16,
  55. weight: "bold",
  56. },
  57. },
  58. xaxis: {
  59. title: data.xaixs || "X轴",
  60. gridcolor: "rgb(255,255,255)",
  61. type: data.xaixs === "机组" ? "category" : undefined,
  62. tickcolor: "rgb(255,255,255)",
  63. backgroundcolor: "#e5ecf6",
  64. },
  65. yaxis: {
  66. title: data.yaixs || "Y轴",
  67. gridcolor: "rgb(255,255,255)",
  68. tickcolor: "rgb(255,255,255)",
  69. backgroundcolor: "#e5ecf6",
  70. title_standoff: 100,
  71. },
  72. margin: {
  73. l: 50,
  74. r: 50,
  75. t: 50,
  76. b: 50,
  77. },
  78. plot_bgcolor: "#e5ecf6",
  79. gridcolor: "#fff",
  80. bgcolor: "#e5ecf6",
  81. autosize: true,
  82. };
  83. // 如果 Y 轴是 "温度偏差",添加两条红色虚线
  84. if (data.yaixs === "温度偏差") {
  85. layout.shapes = [
  86. {
  87. type: "line",
  88. xref: "paper",
  89. yref: "y",
  90. x0: 0,
  91. x1: 1,
  92. y0: 5,
  93. y1: 5,
  94. line: {
  95. color: "red",
  96. width: 2,
  97. dash: "dash",
  98. },
  99. hovertext: "上限: 5°C",
  100. hoverinfo: "text",
  101. },
  102. {
  103. type: "line",
  104. xref: "paper",
  105. yref: "y",
  106. x0: 0,
  107. x1: 1,
  108. y0: -5,
  109. y1: -5,
  110. line: {
  111. color: "red",
  112. width: 2,
  113. dash: "dash",
  114. },
  115. hovertext: "下限: -5°C",
  116. hoverinfo: "text",
  117. },
  118. ];
  119. layout.hovermode = "x unified";
  120. }
  121. // 如果是机组数据,设置刻度值
  122. if (data.xaixs === "机组" || data.xaixs === "机组名称") {
  123. layout.xaxis.tickvals = chartDataset.xData;
  124. layout.xaxis.ticktext = chartDataset.xData;
  125. }
  126. // 创建HTML内容
  127. const htmlContent = `
  128. <!DOCTYPE html>
  129. <html>
  130. <head>
  131. <script>${plotlyContent}</script>
  132. <style>
  133. body { margin: 0; }
  134. #chart { width: 800px; height: 600px; }
  135. </style>
  136. </head>
  137. <body>
  138. <div id="chart"></div>
  139. <script>
  140. window.onload = function() {
  141. const trace = ${JSON.stringify(trace)};
  142. const layout = ${JSON.stringify(layout)};
  143. Plotly.newPlot('chart', [trace], layout).then(() => {
  144. window.chartRendered = true;
  145. });
  146. };
  147. </script>
  148. </body>
  149. </html>
  150. `;
  151. // 设置页面内容
  152. await page.setContent(htmlContent, {
  153. waitUntil: "networkidle0",
  154. });
  155. // 等待图表渲染完成
  156. await page.waitForFunction(() => window.chartRendered === true, {
  157. timeout: 60000,
  158. });
  159. // 截图并保存到临时文件
  160. const chartElement = await page.$("#chart");
  161. await chartElement.screenshot({
  162. path: tempFilePath,
  163. type: "png",
  164. });
  165. // 上传图片到服务器
  166. const formData = new FormData();
  167. formData.append("file", fs.createReadStream(tempFilePath));
  168. formData.append("type", "chart");
  169. formData.append("engineCode", data.engineCode);
  170. formData.append("analysisTypeCode", data.analysisTypeCode);
  171. // const uploadResponse = await axios.post(
  172. // "http://192.168.50.233:6900/upload", //minio 地址上传(http://192.168.50.233:6900/upload)
  173. // formData,
  174. // {
  175. // headers: {
  176. // ...formData.getHeaders(),
  177. // },
  178. // }
  179. // );
  180. //删除临时文件;
  181. // await fs.remove(tempFilePath);
  182. // console.log("图表生成并上传成功:", uploadResponse.data.url);
  183. // return uploadResponse.data.url;
  184. return formData;
  185. } finally {
  186. await browser.close();
  187. }
  188. } catch (error) {
  189. console.error("生成图表失败:", error);
  190. throw error;
  191. }
  192. };