3DDrawingChart.js 7.0 KB

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