Time3DChart.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /*
  2. * @Author: your name
  3. * @Date: 2025-04-14 17:49:33
  4. * @LastEditTime: 2025-07-14 16:04:07
  5. * @LastEditors: bogon
  6. * @Description: In User Settings Edit
  7. * @FilePath: /performance-test/downLoadServer/src/server/utils/chartsCom/Time3DChart.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. // 格式化日期为 YY-MM 格式
  16. const formatDate = (dateString) => {
  17. const date = new Date(dateString);
  18. const year = date.getFullYear(); // 获取年份后两位
  19. const month = ("0" + (date.getMonth() + 1)).slice(-2); // 获取月份并确保两位数
  20. return `${year}-${month}`;
  21. };
  22. export const generateTime3DChart = async (data, bucketName, objectName) => {
  23. try {
  24. // 创建临时目录
  25. const tempDir = path.join(process.cwd(), "images");
  26. await fs.ensureDir(tempDir);
  27. const tempFilePath = path.join(
  28. tempDir,
  29. `temp_heatmap_chart_${Date.now()}.jpeg`
  30. );
  31. // 获取 plotly.js 的绝对路径
  32. const plotlyPath = path.join(
  33. process.cwd(),
  34. "src",
  35. "public",
  36. "js",
  37. "plotly-3.0.1.min.js"
  38. );
  39. const plotlyContent = await fs.readFile(plotlyPath, "utf-8");
  40. // 创建浏览器实例
  41. const browser = await puppeteer.launch({
  42. headless: "new",
  43. // 根据系统改路径
  44. executablePath: `${process.env.CHROME_PATH}`, // 根据系统改路径
  45. args: [
  46. "--no-sandbox",
  47. "--disable-setuid-sandbox",
  48. "--window-size=1920,1080",
  49. ],
  50. });
  51. try {
  52. const page = await browser.newPage();
  53. // 准备图表数据
  54. const uniqueMonths = Array.from(
  55. new Set(data.data[0].yData.map((date) => formatDate(date)))
  56. );
  57. // 设置每个月份对应的颜色
  58. const monthColors = colorSchemes[0].colors;
  59. const traces = uniqueMonths.map((month, monthIndex) => {
  60. const monthData = data.data[0].yData
  61. .map((date, index) => (formatDate(date) === month ? index : -1))
  62. .filter((index) => index !== -1);
  63. return {
  64. x: monthData.map((index) => data.data[0].xData[index]), // 发电机转速
  65. y: monthData.map((index) => data.data[0].yData[index]), // 时间
  66. z: monthData.map((index) => data.data[0].zData[index]), // 有功功率
  67. mode: "markers", // 根据需要设置模式
  68. type: "scatter3d",
  69. marker: {
  70. size: 1, // 使用动态点大小
  71. color: monthColors[monthIndex], // 使用配色方案
  72. colorscale: "YlGnBu",
  73. },
  74. name: ` ${month}`,
  75. };
  76. });
  77. // 准备布局配置
  78. const layout = {
  79. title: {
  80. text: data.data[0].title,
  81. font: {
  82. size: 16,
  83. weight: "bold",
  84. },
  85. },
  86. scene: {
  87. xaxis: {
  88. gridcolor: "#fff",
  89. backgroundcolor: "#e0e7f1",
  90. showbackground: true,
  91. linecolor: "black",
  92. ticks: "outside",
  93. // ticklen: 10,
  94. tickcolor: "black",
  95. zeroline: false,
  96. tickangle: -10,
  97. title: {
  98. text: data.xaixs,
  99. standoff: 100,
  100. },
  101. },
  102. yaxis: {
  103. type: "category", // 让 Y 轴按类别均匀分布
  104. categoryorder: "category ascending", // 按类别字母顺序排列
  105. type: "date",
  106. tickformat: "%Y-%m",
  107. // dtick: "M3",//显式设置每3个月一个刻度
  108. nticks: 3,
  109. gridcolor: "#fff",
  110. tickcolor: "#e5ecf6",
  111. backgroundcolor: "#e0e7f1",
  112. showbackground: true,
  113. linecolor: "black",
  114. ticks: "outside",
  115. tickcolor: "black",
  116. zeroline: false,
  117. tickangle: 25,
  118. title: {
  119. text: data.yaixs,
  120. },
  121. },
  122. zaxis: {
  123. gridcolor: "#fff",
  124. tickcolor: "#fff",
  125. backgroundcolor: "#e0e7f1",
  126. showbackground: true,
  127. linecolor: "black",
  128. ticks: "outside",
  129. tickcolor: "black",
  130. zeroline: false,
  131. nticks: 3,
  132. tickangle: -90,
  133. title: {
  134. text: data.zaixs,
  135. },
  136. },
  137. plot_bgcolor: "#e5ecf6",
  138. gridcolor: "#fff",
  139. bgcolor: "#e5ecf6", // 设置背景颜色
  140. aspectmode: "manual",
  141. aspectratio: {
  142. x: 2.0000000000000018,
  143. y: 1.5454545454545465,
  144. z: 0.9090909090909098,
  145. },
  146. camera: {
  147. up: {
  148. x: 0.19380723218588866,
  149. y: 0.2540224158731985,
  150. z: 0.9475818534492884,
  151. },
  152. center: {
  153. x: -0.052807476121180814,
  154. y: 0.02451796399554085,
  155. z: -0.022911006648570736,
  156. },
  157. eye: {
  158. x: -2.126389777109588,
  159. y: -2.5514260394238466,
  160. z: 1.091739681861482,
  161. },
  162. projection: {
  163. type: "orthographic",
  164. },
  165. },
  166. },
  167. margin: { t: 50, b: 50, l: 50, r: 50 },
  168. staticPlot: false,
  169. showlegend: true,
  170. legend: {
  171. itemsizing: "constant", // ✅ 统一图例 marker 大小
  172. font: {
  173. size: 12,
  174. },
  175. marker: {
  176. size: 10, // 图例中点的大小
  177. },
  178. },
  179. };
  180. // 准备 HTML 内容
  181. const htmlContent = `
  182. <!DOCTYPE html>
  183. <html>
  184. <head>
  185. <meta charset="UTF-8">
  186. <title>3D图</title>
  187. <script>${plotlyContent}</script>
  188. </head>
  189. <body>
  190. <div id="chart" style="width: 100%; height: 500px"></div>
  191. <script>
  192. const traces = ${JSON.stringify(traces)};
  193. const layout = ${JSON.stringify(layout)};
  194. Plotly.newPlot('chart', traces, layout, { responsive: true }).then(() => {
  195. window.chartRendered = true; // 确保在图表渲染完成后设置
  196. }).catch((error) => {
  197. console.error("图表渲染错误:", error); // 捕获渲染错误
  198. });
  199. </script>
  200. </body>
  201. </html>
  202. `;
  203. // 设置页面内容
  204. await page.setContent(htmlContent, {
  205. waitUntil: "networkidle0",
  206. });
  207. // 等待图表渲染完成,延长超时时间
  208. await page.waitForFunction(() => window.chartRendered === true, {
  209. timeout: 150000, // 延长到 120 秒
  210. });
  211. // 截图并保存到临时文件
  212. const chartElement = await page.$("#chart");
  213. await chartElement.screenshot({
  214. path: tempFilePath,
  215. type: "jpeg",
  216. });
  217. // 上传图片到服务器
  218. const formData = new FormData();
  219. formData.append("file", fs.createReadStream(tempFilePath));
  220. // return formData;
  221. // 发送上传请求
  222. const response = await axios.post(
  223. `${process.env.API_BASE_URL}/examples/upload`,
  224. { filePath: tempFilePath, bucketName, objectName }
  225. );
  226. return response?.data?.url;
  227. } catch (error) {
  228. console.error("生成3D图失败:", error);
  229. throw error;
  230. } finally {
  231. await browser.close();
  232. }
  233. } catch (error) {
  234. console.error("生成3D图失败:", error);
  235. throw error;
  236. }
  237. };