3DDrawingChart.js 6.8 KB

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