BoxMarkersCharts.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <template>
  2. <div style="width: 100%; height: 500px">
  3. <!-- 时间范围选择器 -->
  4. <el-date-picker
  5. size="mini"
  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. style="margin: 20px 0 0 0"
  15. ></el-date-picker>
  16. <div
  17. v-loading="loading"
  18. :ref="`plotlyChart-${index}`"
  19. style="width: 100%; height: 450px"
  20. >
  21. <el-empty v-if="isError" description="请求失败"></el-empty>
  22. </div>
  23. </div>
  24. </template>
  25. <script>
  26. import Plotly from "plotly.js-dist-min";
  27. import axios from "axios";
  28. import { myMixin } from "@/mixins/chartRequestMixin";
  29. export default {
  30. name: "BoxPlotWithMedians",
  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: {}, // 存储从 API 获取的原始数据
  48. dateRange: [], // 日期范围选择器的值
  49. loading: false,
  50. isError: false,
  51. };
  52. },
  53. mounted() {
  54. this.getData(); // 获取数据
  55. },
  56. watch: {
  57. setUpImgData: {
  58. handler(newType) {
  59. this.drawBoxPlot();
  60. },
  61. deep: true,
  62. },
  63. },
  64. methods: {
  65. // 获取数据
  66. async getData() {
  67. if (this.fileAddr !== "") {
  68. try {
  69. this.loading = true;
  70. this.cancelToken = axios.CancelToken.source();
  71. const resultChartsData = await axios.get(this.fileAddr, {
  72. cancelToken: this.cancelToken.token,
  73. });
  74. this.chartData = resultChartsData.data;
  75. this.drawBoxPlot(); // 绘制图表
  76. this.isError = false;
  77. this.loading = false;
  78. } catch (error) {
  79. this.isError = true;
  80. this.loading = false;
  81. }
  82. }
  83. },
  84. // 处理日期范围变化
  85. onDateRangeChange() {
  86. this.drawBoxPlot(); // 日期变化时重新绘制图表
  87. },
  88. // 判断时间戳是否在选择的日期范围内
  89. isInDateRange(timestamp) {
  90. const [startDate, endDate] = this.dateRange;
  91. if (!startDate || !endDate) return true;
  92. const date = new Date(timestamp);
  93. return date >= new Date(startDate) && date <= new Date(endDate);
  94. },
  95. // 过滤数据
  96. filterData(group) {
  97. const filteredXData = [];
  98. const filteredYData = [];
  99. const filteredMedians = group.medians ? { x: [], y: [] } : null;
  100. group.medians.x.forEach((timestamp, index) => {
  101. if (this.isInDateRange(timestamp)) {
  102. filteredMedians.x.push(timestamp);
  103. filteredMedians.y.push(group.medians.y[index]);
  104. }
  105. });
  106. group.xData.forEach((timestamp, index) => {
  107. if (this.isInDateRange(timestamp)) {
  108. filteredXData.push(timestamp);
  109. filteredYData.push(group.yData[index]);
  110. }
  111. });
  112. return {
  113. ...group,
  114. xData: filteredXData,
  115. yData: filteredYData,
  116. medians: filteredMedians,
  117. };
  118. },
  119. // 绘制箱线图
  120. drawBoxPlot() {
  121. const chartContainer = this.$refs[`plotlyChart-${this.index}`];
  122. const { data, xaixs, yaixs, analysisTypeCode } = this.chartData;
  123. // 过滤数据,根据选定的日期范围
  124. const filteredData = data.map(this.filterData);
  125. // 创建图表数据
  126. const traces = [];
  127. const medianMarkers = [];
  128. filteredData.forEach((group) => {
  129. traces.push({
  130. x: group.xData,
  131. y: group.yData,
  132. type: "box",
  133. name: group.title,
  134. marker: {
  135. color: group.color,
  136. },
  137. boxpoints: false, // 是否显示异常值
  138. boxmean: true, // 是否显示均值
  139. hovertemplate: (data) => {
  140. // 获取箱线图的统计数据
  141. const max = data.max; // 最大值
  142. const min = data.min; // 最小值
  143. const q1 = data.q1; // 下四分位数
  144. const q3 = data.q3; // 上四分位数
  145. const median = data.median; // 中位数
  146. const mean = data.mean; // 均值
  147. const iqr = q3 - q1; // 四分位距(IQR)
  148. const outliers = data.outliers || "无"; // 异常值(如果没有异常值,则显示“无”)
  149. return (
  150. `${xaixs}: %{x}<br>` + // 显示类别
  151. `<b>最大值</b>: ${max}<br>` + // 最大值
  152. `<b>最小值</b>: ${min}<br>` + // 最小值
  153. `<b>上四分位数 (Q3)</b>: ${q3}<br>` + // 上四分位数
  154. `<b>中位数 (Median)</b>: ${median}<br>` + // 中位数
  155. `<b>下四分位数 (Q1)</b>: ${q1}<br>` + // 下四分位数
  156. `<b>四分位距 (IQR)</b>: ${iqr}<br>` + // 四分位距
  157. `<b>均值 (Mean)</b>: ${mean}<br>` + // 均值
  158. `<b>异常值</b>: ${outliers}<br>` + // 异常值
  159. "<extra></extra>" // 额外的文本(可以留空)
  160. );
  161. },
  162. });
  163. // 如果有中位点数据且中位点数据不为空,添加中位点
  164. if (group.medians && group.medians.x.length > 0) {
  165. medianMarkers.push({
  166. x: group.medians.x,
  167. y: group.medians.y,
  168. mode: "markers",
  169. marker: {
  170. color: "#406DAB",
  171. size: 3,
  172. },
  173. name: `${group.title} - 中位点`,
  174. type: "scatter",
  175. hovertemplate: `${xaixs}: %{x} <br> ${yaixs}: %{y} <br><b>中位点</b>: %{y}<br><extra></extra>`,
  176. });
  177. }
  178. });
  179. const layout = {
  180. title: analysisTypeCode + data[0].engineName,
  181. xaxis: {
  182. title: xaixs,
  183. type: "date",
  184. tickformat: "%Y-%m-%d",
  185. },
  186. yaxis: {
  187. title: yaixs,
  188. },
  189. showlegend: true,
  190. };
  191. const getChartSetUp = (axisTitle) => {
  192. return this.setUpImgData.find((item) => item.text.includes(axisTitle));
  193. };
  194. // 更新x轴和y轴的范围与步长
  195. const xChartSetUp = getChartSetUp(layout.xaxis.title);
  196. if (xChartSetUp) {
  197. layout.xaxis.dtick = xChartSetUp.dtick;
  198. layout.xaxis.range = [xChartSetUp.min, xChartSetUp.max];
  199. }
  200. const yChartSetUp = getChartSetUp(layout.yaxis.title);
  201. if (yChartSetUp) {
  202. layout.yaxis.dtick = yChartSetUp.dtick;
  203. layout.yaxis.range = [yChartSetUp.min, yChartSetUp.max];
  204. }
  205. Plotly.newPlot(chartContainer, [...traces, ...medianMarkers], layout);
  206. },
  207. },
  208. };
  209. </script>
  210. <style scoped>
  211. /* 可根据需要自定义样式 */
  212. </style>