TwoDMarkersChart.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /*
  2. * @Author: your name
  3. * @Date: 2025-04-25 16:19:33
  4. * @LastEditTime: 2025-07-08 14:40:29
  5. * @LastEditors: bogon
  6. * @Description: In User Settings Edit
  7. * @FilePath: /downLoadServer/src/server/utils/chartsCom/TwoDMarkersChart.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. export const generateTwoDMarkersChart = async (
  16. data,
  17. bucketName,
  18. objectName,
  19. ) => {
  20. try {
  21. // 创建临时目录
  22. const tempDir = path.join(process.cwd(), "images");
  23. await fs.ensureDir(tempDir);
  24. const tempFilePath = path.join(
  25. tempDir,
  26. `temp_scatter_chart_${Date.now()}.jpeg`,
  27. );
  28. // 获取 plotly.js 的绝对路径
  29. const plotlyPath = path.join(
  30. process.cwd(),
  31. "src",
  32. "public",
  33. "js",
  34. "plotly-3.0.1.min.js",
  35. );
  36. const plotlyContent = await fs.readFile(plotlyPath, "utf-8");
  37. // 创建浏览器实例
  38. const browser = await puppeteer.launch({
  39. headless: "new",
  40. // 根据系统改路径
  41. executablePath: `${process.env.CHROME_PATH}`, // 根据系统改路径
  42. args: ["--no-sandbox", "--disable-setuid-sandbox"],
  43. });
  44. try {
  45. const page = await browser.newPage();
  46. // 准备图表数据
  47. const chartDataset = data.data[0];
  48. const uniqueTimeLabels =
  49. chartDataset.colorbar &&
  50. chartDataset.colorbar.length === chartDataset.xData.length
  51. ? [...new Set(chartDataset.colorbar)]
  52. : [...new Set(chartDataset.yData)];
  53. const ticktext = uniqueTimeLabels.map((label) => label);
  54. const tickvals = uniqueTimeLabels.map((_, index) => index + 1);
  55. const timeMapping = uniqueTimeLabels.reduce((acc, curr, index) => {
  56. acc[curr] = index + 1;
  57. return acc;
  58. }, {});
  59. // 获取 colorbar 的最小值和最大值来计算比例值
  60. const minValue = Math.min(...new Set(uniqueTimeLabels));
  61. const maxValue = Math.max(...new Set(uniqueTimeLabels));
  62. const colorStops = [
  63. colorSchemes[0].colors[0],
  64. colorSchemes[0].colors[4],
  65. colorSchemes[0].colors[8],
  66. colorSchemes[0].colors[12],
  67. ];
  68. // 计算渐变比例
  69. const colors = colorStops.map((color, index) => {
  70. const proportion = index / (colorStops.length - 1); // 计算比例值 (0, 1/3, 2/3, 1)
  71. return [proportion, color]; // 创建比例-颜色映射
  72. });
  73. // 确保 colors 至少有 2 种颜色,否则使用默认颜色
  74. if (colors.length < 2) {
  75. colors.push([1, colorStops[colorStops.length - 1] || "#1B2973"]);
  76. }
  77. // 计算颜色值映射
  78. let colorValues =
  79. chartDataset.colorbar &&
  80. chartDataset.colorbar.length === chartDataset.xData.length
  81. ? chartDataset.colorbar.map((date) => timeMapping[date])
  82. : chartDataset.yData.map((date) => timeMapping[date]);
  83. // 绘制 2D 散点图
  84. const trace = {
  85. x: chartDataset.xData,
  86. y: chartDataset.yData,
  87. mode: "markers",
  88. type: "scattergl", // 使用 scattergl 提高性能
  89. text: chartDataset.engineName, // 提示文本
  90. marker: {
  91. color: colorValues,
  92. colorscale: [
  93. [0, "#F9FDD2"],
  94. [0.15, "#E9F6BD"],
  95. [0.3, "#C2E3B9"],
  96. [0.45, "#8AC8BE"],
  97. [0.6, "#5CA8BF"],
  98. [0.75, "#407DB3"],
  99. [0.9, "#2E4C9A"],
  100. [1, "#1B2973"],
  101. ],
  102. size: new Array(chartDataset.xData.length).fill(6), // 点的大小
  103. },
  104. };
  105. // 图表布局
  106. const layout = {
  107. title: {
  108. text: chartDataset.title,
  109. font: {
  110. size: 16,
  111. weight: "bold",
  112. },
  113. },
  114. xaxis: {
  115. title: {
  116. text: data.xaixs,
  117. },
  118. gridcolor: "rgb(255,255,255)",
  119. tickcolor: "rgb(255,255,255)",
  120. backgroundcolor: "#e5ecf6",
  121. showbackground: true,
  122. },
  123. yaxis: {
  124. title: {
  125. text: data.yaixs,
  126. },
  127. gridcolor: "rgb(255,255,255)",
  128. tickcolor: "rgb(255,255,255)",
  129. backgroundcolor: "#e5ecf6",
  130. showbackground: true,
  131. },
  132. // showlegend: true,
  133. plot_bgcolor: "#e5ecf6",
  134. gridcolor: "#fff",
  135. };
  136. // 准备 HTML 内容
  137. const htmlContent = `
  138. <!DOCTYPE html>
  139. <html>
  140. <head>
  141. <meta charset="UTF-8">
  142. <title>2D 散点图</title>
  143. <script>${plotlyContent}</script>
  144. </head>
  145. <body>
  146. <div id="chart" style="width: 100%; height: 600px"></div>
  147. <script>
  148. const traces = [${JSON.stringify(trace)}];
  149. const layout = ${JSON.stringify(layout)};
  150. Plotly.newPlot('chart', traces, layout, { responsive: true }).then(() => {
  151. window.chartRendered = true; // 确保在图表渲染完成后设置
  152. console.log("图表渲染完成");
  153. }).catch((error) => {
  154. console.error("图表渲染错误:", error); // 捕获渲染错误
  155. });
  156. </script>
  157. </body>
  158. </html>
  159. `;
  160. // 设置页面内容
  161. await page.setContent(htmlContent, {
  162. waitUntil: "networkidle0",
  163. });
  164. // 等待图表渲染完成,延长超时时间
  165. await page.waitForFunction(() => window.chartRendered === true, {
  166. timeout: 150000, // 延长到 150 秒
  167. });
  168. // 截图并保存到临时文件
  169. const chartElement = await page.$("#chart");
  170. await chartElement.screenshot({
  171. path: tempFilePath,
  172. type: "jpeg",
  173. });
  174. // 上传图片到服务器
  175. const formData = new FormData();
  176. formData.append("file", fs.createReadStream(tempFilePath));
  177. // return formData;
  178. // 发送上传请求
  179. const response = await axios.post(
  180. `${process.env.API_BASE_URL}/examples/upload`,
  181. { filePath: tempFilePath, bucketName, objectName },
  182. );
  183. return response?.data?.url;
  184. } catch (error) {
  185. console.error("生成2D散点图失败:", error);
  186. throw error;
  187. } finally {
  188. await browser.close();
  189. }
  190. } catch (error) {
  191. console.error("生成2D散点图失败:", error);
  192. throw error;
  193. }
  194. };