BoxLineCharts.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <template>
  2. <div style="width: 100%; height: 500px">
  3. <!-- 条件渲染日期范围选择器 -->
  4. <el-date-picker
  5. v-if="chartData.xaixs === '时间'"
  6. v-model="dateRange"
  7. type="daterange"
  8. align="right"
  9. unlink-panels
  10. range-separator="至"
  11. start-placeholder="开始日期"
  12. end-placeholder="结束日期"
  13. @change="onDateRangeChange"
  14. size="mini"
  15. style="margin: 20px 0 0 0"
  16. ></el-date-picker>
  17. <div
  18. v-loading="loading"
  19. :id="`plotDivBox-${index}`"
  20. style="width: 100%; height: 450px"
  21. >
  22. <el-empty v-if="isError" description="请求失败"></el-empty>
  23. </div>
  24. </div>
  25. </template>
  26. <script>
  27. import Plotly from "plotly.js-dist";
  28. import axios from "axios";
  29. import { myMixin } from "@/mixins/chartRequestMixin";
  30. export default {
  31. mixins: [myMixin],
  32. props: {
  33. fileAddr: {
  34. default: "",
  35. type: String,
  36. },
  37. index: {
  38. type: String,
  39. },
  40. setUpImgData: {
  41. default: () => [],
  42. type: Array,
  43. },
  44. },
  45. data() {
  46. return {
  47. chartData: {},
  48. dateRange: [], // 用于存储选中的日期范围
  49. loading: false,
  50. isError: false,
  51. };
  52. },
  53. watch: {
  54. setUpImgData: {
  55. handler(newType) {
  56. this.drawBoxPlot();
  57. },
  58. deep: true,
  59. },
  60. dateRange: {
  61. handler(newRange) {
  62. this.drawBoxPlot(); // 日期范围变化时重新绘制图表
  63. },
  64. deep: true,
  65. },
  66. },
  67. mounted() {
  68. this.getData();
  69. },
  70. methods: {
  71. async getData() {
  72. if (this.fileAddr !== "") {
  73. try {
  74. this.loading = true;
  75. this.cancelToken = axios.CancelToken.source();
  76. const resultChartsData = await axios.get(this.fileAddr, {
  77. cancelToken: this.cancelToken.token,
  78. });
  79. this.chartData = resultChartsData.data;
  80. this.loading = false;
  81. this.isError = false;
  82. this.drawBoxPlot(); // 数据加载完成后绘制图表
  83. } catch (error) {
  84. this.loading = false;
  85. this.isError = true;
  86. }
  87. }
  88. },
  89. // 处理日期范围变化
  90. onDateRangeChange() {
  91. this.drawBoxPlot(); // 日期变化时重新绘制图表
  92. },
  93. // 判断时间戳是否在选择的日期范围内
  94. isInDateRange(timestamp) {
  95. const [startDate, endDate] = this.dateRange;
  96. if (!startDate || !endDate) return true;
  97. const date = new Date(timestamp);
  98. return date >= new Date(startDate) && date <= new Date(endDate);
  99. },
  100. // 判断xData是否为日期格式
  101. isDateType(xData) {
  102. if (xData) {
  103. const firstTimestamp = xData[0];
  104. return !isNaN(Date.parse(firstTimestamp)); // 判断是否是有效日期
  105. } else {
  106. return false;
  107. }
  108. },
  109. // 过滤数据
  110. filterData(group) {
  111. const filteredXData = [];
  112. const filteredYData = [];
  113. const filteredMedians = group.medians ? { x: [], y: [] } : null;
  114. if (this.isDateType(group.xData)) {
  115. group.xData.forEach((timestamp, index) => {
  116. if (this.isInDateRange(timestamp)) {
  117. filteredXData.push(timestamp);
  118. filteredYData.push(group.yData[index]);
  119. if (filteredMedians && this.isInDateRange(group.medians.x[index])) {
  120. filteredMedians.x.push(group.medians.x[index]);
  121. filteredMedians.y.push(group.medians.y[index]);
  122. }
  123. }
  124. });
  125. } else {
  126. filteredXData.push(...group.xData);
  127. filteredYData.push(...group.yData);
  128. if (group.medians) {
  129. filteredMedians.x.push(...group.medians.x);
  130. filteredMedians.y.push(...group.medians.y);
  131. }
  132. }
  133. return {
  134. ...group,
  135. xData: filteredXData,
  136. yData: filteredYData,
  137. medians: filteredMedians,
  138. };
  139. },
  140. // 绘制箱线图
  141. drawBoxPlot() {
  142. if (!this.chartData.data || this.loading) return; // 如果数据为空或正在加载则不绘制
  143. const chartData = this.chartData.data[0];
  144. const filteredData = this.filterData(chartData);
  145. const trace = {
  146. x: filteredData.xData,
  147. y: filteredData.yData,
  148. type: "box",
  149. marker: {
  150. color: "#263649",
  151. },
  152. line: {
  153. width: 0.8, // 这里设置线条粗细(默认是 2)
  154. },
  155. boxpoints: false,
  156. boxmean: false,
  157. name: filteredData.title,
  158. fillcolor: "#458EF7", // 设置箱线图填充颜色,带透明度
  159. hovertemplate:
  160. `${this.chartData.xaixs}:` +
  161. ` %{x} <br> ` +
  162. `${this.chartData.yaixs}:` +
  163. "%{y} <br>",
  164. };
  165. let trace2 = {};
  166. if (filteredData.medians && filteredData.medians.x.length > 0) {
  167. trace2 = {
  168. x: filteredData.medians.x,
  169. y: filteredData.medians.y,
  170. mode: "markers",
  171. marker: {
  172. color: "#406DAB",
  173. size: 3,
  174. },
  175. name: `${filteredData.title} - 中位点`,
  176. type: "scatter",
  177. };
  178. }
  179. const layout = {
  180. title: {
  181. text: filteredData.title,
  182. font: {
  183. size: 16,
  184. weight: "bold",
  185. },
  186. },
  187. xaxis: {
  188. title: this.chartData.xaixs,
  189. tickmode: "array",
  190. type: "category", // 让 Y 轴按类别均匀分布
  191. gridcolor: "rgb(255,255,255)",
  192. tickcolor: "rgb(255,255,255)",
  193. backgroundcolor: "#e5ecf6",
  194. },
  195. yaxis: {
  196. title: this.chartData.yaixs,
  197. gridcolor: "rgb(255,255,255)",
  198. tickcolor: "rgb(255,255,255)",
  199. backgroundcolor: "#e5ecf6",
  200. },
  201. showlegend: false,
  202. plot_bgcolor: "#e5ecf6",
  203. gridcolor: "#fff",
  204. bgcolor: "#e5ecf6", // 设置背景颜色
  205. };
  206. if (
  207. this.chartData.xaixs === "机组" ||
  208. this.chartData.xaixs === "机组名称"
  209. ) {
  210. layout.xaxis.tickvals = [...new Set(filteredData.xData)];
  211. layout.xaxis.ticktext = [...new Set(filteredData.xData)];
  212. }
  213. const getChartSetUp = (axisTitle) => {
  214. return this.setUpImgData.find((item) => item.text.includes(axisTitle));
  215. };
  216. const xChartSetUp = getChartSetUp(layout.xaxis.title);
  217. if (xChartSetUp) {
  218. layout.xaxis.dtick = xChartSetUp.dtick;
  219. layout.xaxis.range = [xChartSetUp.min, xChartSetUp.max];
  220. }
  221. const yChartSetUp = getChartSetUp(layout.yaxis.title);
  222. if (yChartSetUp) {
  223. layout.yaxis.dtick = yChartSetUp.dtick;
  224. layout.yaxis.range = [yChartSetUp.min, yChartSetUp.max];
  225. }
  226. this.$nextTick(() => {
  227. const plotDiv = document.getElementById(`plotDivBox-${this.index}`);
  228. if (plotDiv) {
  229. Plotly.newPlot(plotDiv, [trace, trace2], layout, {
  230. responsive: true,
  231. modeBarButtonsToRemove: [
  232. "sendDataToCloud",
  233. // "autoScale2d",
  234. // "hoverClosest3d",
  235. "resetCameraLastSave3d",
  236. "resetCameraDefault3d",
  237. "resetCameraLastSave",
  238. "sendDataToCloud",
  239. // "pan2d", // 平移按钮
  240. "zoom2d", // 缩放按钮
  241. // "zoom",
  242. "zoom3d",
  243. // "select2d", // 选择框
  244. // "lasso2d", // 套索选择
  245. // "resetScale2d", // 重置轴
  246. // // "zoomIn", // 放大
  247. // // "zoomOut", // 缩小
  248. // "home", // 重置
  249. // "toImage", // 导出为图片
  250. // "hoverClosestCartesian", // 悬浮信息
  251. // "zoomIn2d", // 缩放按钮(详细版本)
  252. // "zoomOut2d", // 缩放按钮(详细版本)
  253. // "autoScale2D",
  254. "plotlylogo2D",
  255. "plotlylogo3D",
  256. // "Produced with Plotly.js(v2.35.2)", // 删除 Plotly logo
  257. ],
  258. displaylogo: false,
  259. }).then(function (gd) {
  260. // 获取工具栏按钮
  261. const toolbar = gd.querySelector(".modebar");
  262. const buttons = toolbar.querySelectorAll(".modebar-btn");
  263. // 定义一个映射对象,方便修改按钮提示
  264. const titleMap = {
  265. "Download plot as a png": "保存图片",
  266. Autoscale: "缩放",
  267. Pan: "平移",
  268. "Zoom out": "放大",
  269. "Zoom in": "缩小",
  270. "Box Select": "选择框操作",
  271. "Lasso Select": "套索选择操作",
  272. "Reset axes": "重置操作",
  273. "Reset camera to default": "重置相机视角",
  274. "Turntable rotation": "转台式旋转",
  275. "Orbital rotation": "轨道式旋转",
  276. };
  277. // 遍历所有按钮,修改它们的 title
  278. buttons.forEach(function (button) {
  279. const dataTitle = button.getAttribute("data-title");
  280. // 如果标题匹配,修改属性值
  281. if (titleMap[dataTitle]) {
  282. button.setAttribute("data-title", titleMap[dataTitle]);
  283. }
  284. });
  285. });
  286. }
  287. });
  288. },
  289. },
  290. };
  291. </script>
  292. <style scoped>
  293. /* 可根据需要自定义样式 */
  294. </style>