Time3DBarChart.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. <!--
  2. * @Author: your name
  3. * @Date: 2025-07-24 10:33:42
  4. * @LastEditTime: 2025-08-08 09:33:02
  5. * @LastEditors: bogon
  6. * @Description: In User Settings Edit
  7. * @FilePath: /performance-test/src/views/performance/components/chartsCom/Time3DBarChart.vue
  8. -->
  9. <template>
  10. <div>
  11. <!-- 配色方案选择和图表类型切换 -->
  12. <div style="display: flex; align-items: center; padding-top: 20px">
  13. <div style="margin-right: 20px; display: flex; align-items: center">
  14. <el-select
  15. size="small"
  16. v-model="color1"
  17. @change="updateChartColor"
  18. placeholder="选择配色方案"
  19. style="width: 200px"
  20. >
  21. <el-option
  22. v-for="(scheme, index) in colorSchemes"
  23. :key="index"
  24. :label="scheme.label"
  25. :value="scheme.colors"
  26. >
  27. <span
  28. v-for="color in scheme.colors.slice(0, 8)"
  29. :style="{
  30. background: color,
  31. width: '20px',
  32. height: '20px',
  33. display: 'inline-block',
  34. }"
  35. ></span>
  36. </el-option>
  37. </el-select>
  38. </div>
  39. <!-- 点大小控制 -->
  40. <div style="display: flex; align-items: center">
  41. <el-slider
  42. v-model="pointSize"
  43. :min="1"
  44. :max="15"
  45. :step="1"
  46. label="点的大小"
  47. show-stops
  48. style="width: 150px"
  49. @change="updateChartColor"
  50. ></el-slider>
  51. </div>
  52. </div>
  53. <div
  54. v-loading="loading"
  55. :id="`plotly-3d-chart-` + index"
  56. ref="plotlyChart"
  57. style="height: 600px; background-color: #e5ecf6"
  58. ></div>
  59. </div>
  60. </template>
  61. <script>
  62. import Plotly from "plotly.js-dist";
  63. import { myMixin } from "@/mixins/chartRequestMixin";
  64. import { colorSchemes } from "@/views/overview/js/colors";
  65. import { mapState } from "vuex";
  66. export default {
  67. props: {
  68. index: {
  69. default: "",
  70. type: String,
  71. },
  72. chartData: {
  73. default: () => {},
  74. type: Object,
  75. },
  76. setUpImgData: {
  77. default: () => [],
  78. type: Array,
  79. },
  80. },
  81. data() {
  82. return {
  83. color1: [], // 默认颜色
  84. // chartData: {},
  85. chartType: "scatter", // 当前图表类型(默认是散点图)
  86. colorSchemes: [...colorSchemes],
  87. pointSize: 1, // 默认点大小
  88. };
  89. },
  90. mixins: [myMixin],
  91. computed: {
  92. ...mapState("themes", {
  93. themeColor: "themeColor",
  94. }),
  95. },
  96. watch: {
  97. themeColor: {
  98. handler(newval) {
  99. if (newval.length === 0) {
  100. this.color1 = this.colorSchemes[0].colors;
  101. } else {
  102. this.color1 = newval;
  103. }
  104. this.updateChartColor();
  105. },
  106. deep: true,
  107. },
  108. setUpImgData: {
  109. handler(newType) {
  110. this.renderChart();
  111. },
  112. deep: true,
  113. },
  114. chartData: {
  115. handler(newType) {
  116. console.log(newType, "newType");
  117. this.renderChart();
  118. },
  119. deep: true,
  120. },
  121. },
  122. async mounted() {
  123. this.$nextTick(() => {
  124. // this.getData();
  125. this.color1 = this.colorSchemes[0].colors;
  126. this.renderChart();
  127. });
  128. },
  129. methods: {
  130. formatDate(dateString) {
  131. const date = new Date(dateString);
  132. const year = date.getFullYear(); // 获取年份后两位
  133. const month = ("0" + (date.getMonth() + 1)).slice(-2); // 获取月份并确保两位数
  134. return `${year}-${month}`;
  135. },
  136. renderChart() {
  137. const colorMap = {}; // 记录每个 fault_detail 分配的颜色
  138. const colorPalette = [...colorSchemes[0].colors];
  139. let colorIndex = 0;
  140. const traces = [];
  141. if (!this.color1) {
  142. this.color1 = colorSchemes[0].colors;
  143. }
  144. this.chartData &&
  145. this.chartData.data.forEach((item) => {
  146. const fault = item.fault_detail;
  147. const yVal = item.fault_time_sum;
  148. const zVal = item.count;
  149. // 分配唯一颜色
  150. if (!colorMap[fault]) {
  151. colorMap[fault] = colorPalette[colorIndex % colorPalette.length];
  152. colorIndex++;
  153. }
  154. const trace = {
  155. type: "scatter3d",
  156. mode: "lines",
  157. x: [fault, fault, null],
  158. y: [yVal, yVal, null],
  159. z: [0, zVal, null],
  160. line: {
  161. color: colorMap[fault],
  162. width: 16,
  163. },
  164. name: fault,
  165. hovertemplate:
  166. `故障代码: %{x}<br>` +
  167. `累计时间: %{y}<br>` +
  168. `出现次数: %{z}<extra></extra>`,
  169. showlegend: true,
  170. };
  171. traces.push(trace);
  172. });
  173. const layout = {
  174. title: {
  175. text: "故障代码出现次数",
  176. font: { size: 16, weight: "bold" },
  177. },
  178. scene: {
  179. xaxis: {
  180. title: { text: "故障代码", standoff: 100 },
  181. showgrid: true,
  182. zeroline: false,
  183. gridcolor: "#fff",
  184. backgroundcolor: "#e0e7f1",
  185. showbackground: true,
  186. linecolor: "black",
  187. ticks: "outside",
  188. ticklen: 10,
  189. tickcolor: "black",
  190. // tickangle: -10,
  191. },
  192. yaxis: {
  193. title: { text: "累计时间" },
  194. showgrid: true,
  195. zeroline: false,
  196. gridcolor: "#fff",
  197. backgroundcolor: "#e0e7f1",
  198. showbackground: true,
  199. linecolor: "black",
  200. ticks: "outside",
  201. tickangle: 25,
  202. },
  203. zaxis: {
  204. title: { text: "出现次数" },
  205. showgrid: true,
  206. zeroline: false,
  207. gridcolor: "#fff",
  208. backgroundcolor: "#e0e7f1",
  209. showbackground: true,
  210. linecolor: "black",
  211. ticks: "outside",
  212. tickangle: -90,
  213. },
  214. bgcolor: "#e5ecf6",
  215. aspectratio: { x: 2.2, y: 1.7, z: 1 },
  216. aspectmode: "manual",
  217. camera: {
  218. up: {
  219. x: 0.200292643688136,
  220. y: 0.2488259353493132,
  221. z: 0.947612004346693,
  222. },
  223. center: {
  224. x: -0.052807476121180814,
  225. y: 0.02451796399554085,
  226. z: -0.022911006648570736,
  227. },
  228. eye: {
  229. x: -2.126379643342493,
  230. y: -2.551422475965373,
  231. z: 1.0917667684145647,
  232. },
  233. projection: {
  234. type: "orthographic",
  235. },
  236. },
  237. },
  238. margin: { t: 50, b: 50, l: 50, r: 50 },
  239. staticPlot: false,
  240. showlegend: true,
  241. legend: {
  242. x: 1.02,
  243. y: 1,
  244. marker: { size: 10 },
  245. font: {
  246. size: 10,
  247. },
  248. itemsizing: "constant",
  249. itemwidth: 2, // 设置图例项的宽度
  250. },
  251. };
  252. const config = {
  253. modeBarButtonsToAdd: [
  254. {
  255. name: "还原",
  256. icon: Plotly.Icons.home,
  257. click: () => this.resetCamera(),
  258. },
  259. ],
  260. modeBarButtonsToRemove: [
  261. "sendDataToCloud",
  262. "resetCameraLastSave3d",
  263. "resetCameraDefault3d",
  264. "resetCameraLastSave",
  265. "zoom2d",
  266. "zoom3d",
  267. "plotlylogo2D",
  268. "plotlylogo3D",
  269. ],
  270. responsive: true,
  271. displaylogo: false,
  272. };
  273. // 设置轴范围和间距(如果配置了)
  274. const getChartSetUp = (axisTitle) => {
  275. return this.setUpImgData.find((item) => item.text.includes(axisTitle));
  276. };
  277. const xSetup = getChartSetUp(layout.scene.xaxis.title.text);
  278. if (xSetup) {
  279. layout.scene.xaxis.dtick = xSetup.dtick;
  280. layout.scene.xaxis.range = [xSetup.min, xSetup.max];
  281. }
  282. const ySetup = getChartSetUp(layout.scene.yaxis.title.text);
  283. if (ySetup) {
  284. layout.scene.yaxis.dtick = ySetup.dtick;
  285. layout.scene.yaxis.range = [ySetup.min, ySetup.max];
  286. }
  287. const zSetup = getChartSetUp(layout.scene.zaxis.title.text);
  288. if (zSetup) {
  289. layout.scene.zaxis.dtick = zSetup.dtick;
  290. layout.scene.zaxis.range = [zSetup.min, zSetup.max];
  291. }
  292. Plotly.newPlot(
  293. `plotly-3d-chart-${this.index}`,
  294. traces,
  295. layout,
  296. config
  297. ).then((gd) => {
  298. const toolbar = gd.querySelector(".modebar");
  299. const buttons = toolbar.querySelectorAll(".modebar-btn");
  300. const legends = document.querySelectorAll(".legendtext");
  301. legends.forEach((el) => {
  302. const fullText = el.textContent;
  303. el.setAttribute("data-fulltext", fullText);
  304. });
  305. const titleMap = {
  306. "Download plot as a png": "保存图片",
  307. Autoscale: "缩放",
  308. Pan: "平移",
  309. "Zoom out": "缩小",
  310. "Zoom in": "放大",
  311. "Box Select": "选择框操作",
  312. "Lasso Select": "套索选择操作",
  313. "Reset axes": "重置操作",
  314. "Reset camera to default": "重置相机视角",
  315. "Turntable rotation": "转台式旋转",
  316. "Orbital rotation": "轨道式旋转",
  317. };
  318. buttons.forEach((btn) => {
  319. const dataTitle = btn.getAttribute("data-title");
  320. if (titleMap[dataTitle]) {
  321. btn.setAttribute("data-title", titleMap[dataTitle]);
  322. }
  323. });
  324. });
  325. const plotElement = document.getElementById(
  326. `plotly-3d-chart-${this.index}`
  327. );
  328. plotElement.on("plotly_relayout", (eventData) => {
  329. if (eventData["scene.camera"]) {
  330. console.log(
  331. "当前相机视角:",
  332. eventData["scene.camera"],
  333. eventData["scene.aspectratio"]
  334. );
  335. }
  336. });
  337. },
  338. resetCamera() {
  339. Plotly.relayout(`plotly-3d-chart-` + this.index, {
  340. "scene.camera": {
  341. up: {
  342. x: 0.200292643688136,
  343. y: 0.2488259353493132,
  344. z: 0.947612004346693,
  345. },
  346. center: {
  347. x: -0.052807476121180814,
  348. y: 0.02451796399554085,
  349. z: -0.022911006648570736,
  350. },
  351. eye: {
  352. x: -2.126379643342493,
  353. y: -2.551422475965373,
  354. z: 1.0917667684145647,
  355. },
  356. projection: {
  357. type: "orthographic",
  358. },
  359. },
  360. "scene.aspectratio": {
  361. x: 2.2,
  362. y: 1.7,
  363. z: 1,
  364. },
  365. });
  366. },
  367. updateChartColor() {
  368. this.renderChart(); // 当配色方案或点大小发生变化时重新渲染图表
  369. },
  370. // 获取配色选项样式
  371. getOptionStyle(scheme) {
  372. return {
  373. background: `linear-gradient(to right, ${scheme
  374. .slice(0, 8)
  375. .join(", ")})`,
  376. color: "#fff",
  377. height: "30px",
  378. lineHeight: "30px",
  379. borderRadius: "0px",
  380. };
  381. },
  382. },
  383. };
  384. </script>
  385. <style scoped>
  386. /* 样式可以根据需求自定义 */
  387. #plotly-3d-chart {
  388. width: 100%;
  389. height: 600px;
  390. }
  391. ::v-deep canvas {
  392. /* height: 400px !important; */
  393. }
  394. /* 放在你页面的样式中 */
  395. .legendtext {
  396. max-width: 30px;
  397. display: inline-block;
  398. overflow: hidden;
  399. text-overflow: ellipsis;
  400. white-space: nowrap;
  401. vertical-align: top;
  402. position: relative;
  403. cursor: pointer;
  404. }
  405. .legendtext:hover::after {
  406. content: attr(data-fulltext);
  407. position: absolute;
  408. left: 100%;
  409. top: 0;
  410. transform: translateX(6px);
  411. max-width: 300px;
  412. background: rgba(0, 0, 0, 0.85);
  413. color: #fff;
  414. font-size: 12px;
  415. padding: 4px 6px;
  416. border-radius: 4px;
  417. z-index: 9999;
  418. white-space: normal;
  419. box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
  420. }
  421. </style>