ModelIterationVisualization.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <script setup lang='ts'>
  2. import { ref, onMounted, nextTick, onUnmounted, defineProps } from 'vue';
  3. import VueEcharts from 'vue-echarts';
  4. import 'echarts';
  5. import request from '../../../utils/request';
  6. interface HistoryDataItem {
  7. dataset_id: number;
  8. row_count: number;
  9. model_id: number;
  10. model_name: string;
  11. performance_score: number;
  12. timestamp: string;
  13. }
  14. interface HistoryDataResponse {
  15. data_type: string;
  16. timestamps: string[];
  17. row_counts: number[];
  18. performance_scores: number[];
  19. model_details: HistoryDataItem[];
  20. }
  21. interface ScatterDataResponse {
  22. scatter_data: [number, number][];
  23. r2_score: number;
  24. y_range: [number, number];
  25. model_name: string;
  26. model_type: string;
  27. }
  28. const props = defineProps({
  29. showLineChart: {
  30. type: Boolean,
  31. default: true
  32. },
  33. showInitScatterChart: {
  34. type: Boolean,
  35. default: true
  36. },
  37. showMidScatterChart: {
  38. type: Boolean,
  39. default: true
  40. },
  41. showFinalScatterChart: {
  42. type: Boolean,
  43. default: true
  44. },
  45. lineChartPathParam: {
  46. type: String,
  47. default: 'reduce'
  48. },
  49. initScatterModelId: {
  50. type: Number,
  51. default: 6
  52. },
  53. midScatterModelId: {
  54. type: Number,
  55. default: 7
  56. },
  57. finalScatterModelId: {
  58. type: Number,
  59. default: 17
  60. }
  61. });
  62. // 定义响应式变量
  63. const ecLineOption = ref({});
  64. const ecInitScatterOption = ref({});
  65. const ecMidScatterOption = ref({});
  66. const ecFinalScatterOption = ref({});
  67. // 定义图表引用
  68. const ecLineOptionRef = ref<InstanceType<typeof VueEcharts>>();
  69. const ecInitScatterOptionRef = ref<InstanceType<typeof VueEcharts>>();
  70. const ecMidScatterOptionRef = ref<InstanceType<typeof VueEcharts>>();
  71. const ecFinalScatterOptionRef = ref<InstanceType<typeof VueEcharts>>();
  72. // 计算数据范围的函数
  73. const calculateDataRange = (data: [number, number][]) => {
  74. const xValues = data.map(item => item[0]);
  75. const yValues = data.map(item => item[1]);
  76. return {
  77. xMin: Math.min(...xValues),
  78. xMax: Math.max(...xValues),
  79. yMin: Math.min(...yValues),
  80. yMax: Math.max(...yValues)
  81. };
  82. };
  83. // 获取折线图数据
  84. const fetchLineData = async () => {
  85. try {
  86. const response = await request.get<HistoryDataResponse>(`/get-model-history/${props.lineChartPathParam}`);
  87. const data = response.data;
  88. const timestamps = data.timestamps;
  89. const performanceScores: Record<string, number[]> = {};
  90. data.model_details.forEach((item: HistoryDataItem) => {
  91. const score = Number(item.performance_score);
  92. if (!performanceScores[item.model_name]) {
  93. performanceScores[item.model_name] = [];
  94. }
  95. performanceScores[item.model_name].push(score);
  96. });
  97. const series = Object.keys(performanceScores).map(modelName => ({
  98. name: modelName,
  99. type: 'line',
  100. data: performanceScores[modelName]
  101. }));
  102. ecLineOption.value = {
  103. tooltip: {
  104. trigger: 'axis'
  105. },
  106. legend: {
  107. data: Object.keys(performanceScores)
  108. },
  109. grid: {
  110. left: '3%',
  111. right: '17%',
  112. bottom: '3%',
  113. containLabel: true
  114. },
  115. xAxis: {
  116. name: '模型迭代',
  117. type: 'category',
  118. boundaryGap: false,
  119. data: timestamps.map((_, index) => `${index + 1}代`)
  120. },
  121. yAxis: {
  122. name: 'Score (R^2)',
  123. type: 'value'
  124. },
  125. series
  126. };
  127. console.log('ecLineOption updated:', ecLineOption.value);
  128. } catch (error) {
  129. console.error('获取折线图数据失败:', error);
  130. }
  131. };
  132. // 获取散点图数据
  133. const fetchScatterData = async (modelId: number, optionRef: any) => {
  134. try {
  135. const response = await request.get<ScatterDataResponse>(`/model-scatter-data/${modelId}`);
  136. const data = response.data;
  137. const scatterData = data.scatter_data;
  138. const range = calculateDataRange(scatterData);
  139. const padding = 0.1;
  140. const xMin = range.xMin - Math.abs(range.xMin * padding);
  141. const xMax = range.xMax + Math.abs(range.xMax * padding);
  142. const yMin = range.yMin - Math.abs(range.yMin * padding);
  143. const yMax = range.yMax + Math.abs(range.yMax * padding);
  144. const min = Math.min(xMin, yMin);
  145. const max = Math.max(xMax, yMax);
  146. optionRef.value = {
  147. tooltip: {
  148. trigger: 'axis',
  149. axisPointer: {
  150. type: 'cross'
  151. }
  152. },
  153. legend: {
  154. data: ['True vs Predicted']
  155. },
  156. grid: {
  157. left: '3%',
  158. right: '22%',
  159. bottom: '3%',
  160. containLabel: true
  161. },
  162. xAxis: {
  163. name: 'True Values',
  164. type: 'value',
  165. min: min,
  166. max: max
  167. },
  168. yAxis: {
  169. name: 'Predicted Values',
  170. type: 'value',
  171. min: parseFloat(min.toFixed(2)),
  172. max: parseFloat(max.toFixed(2))
  173. },
  174. series: [
  175. {
  176. name: 'True vs Predicted',
  177. type: 'scatter',
  178. data: scatterData,
  179. symbolSize: 10,
  180. itemStyle: {
  181. color: '#1f77b4',
  182. opacity: 0.7
  183. }
  184. },
  185. {
  186. name: 'Trendline',
  187. type: 'line',
  188. data: [
  189. [min, min],
  190. [max, max]
  191. ],
  192. lineStyle: {
  193. type: 'dashed',
  194. color: '#ff7f0e',
  195. width: 2
  196. }
  197. }
  198. ]
  199. };
  200. } catch (error) {
  201. console.error('获取散点图数据失败:', error);
  202. }
  203. };
  204. // 定义调整图表大小的函数
  205. const resizeCharts = () => {
  206. nextTick(() => {
  207. if (props.showLineChart) ecLineOptionRef.value?.resize();
  208. if (props.showInitScatterChart) ecInitScatterOptionRef.value?.resize();
  209. if (props.showMidScatterChart) ecMidScatterOptionRef.value?.resize();
  210. if (props.showFinalScatterChart) ecFinalScatterOptionRef.value?.resize();
  211. });
  212. };
  213. onMounted(async () => {
  214. if (props.showLineChart) await fetchLineData();
  215. if (props.showInitScatterChart) await fetchScatterData(props.initScatterModelId, ecInitScatterOption);
  216. if (props.showMidScatterChart) await fetchScatterData(props.midScatterModelId, ecMidScatterOption);
  217. if (props.showFinalScatterChart) await fetchScatterData(props.finalScatterModelId, ecFinalScatterOption);
  218. // 页面加载完成后调整图表大小
  219. resizeCharts();
  220. // 监听窗口大小变化,调整图表大小
  221. window.addEventListener('resize', resizeCharts);
  222. });
  223. // 组件卸载时移除事件监听器
  224. onUnmounted(() => {
  225. window.removeEventListener('resize', resizeCharts);
  226. });
  227. </script>
  228. <template>
  229. <div class="container">
  230. <template v-if="showLineChart">
  231. <!-- 折线图表头 -->
  232. <div class="chart-container">
  233. <VueEcharts :option="ecLineOption" ref="ecLineOptionRef" />
  234. </div>
  235. </template>
  236. <template v-if="showInitScatterChart">
  237. <!-- 初代散点图表头 -->
  238. <h2 class="chart-header">初代散点图</h2>
  239. <div class="chart-container">
  240. <VueEcharts :option="ecInitScatterOption" ref="ecInitScatterOptionRef" />
  241. </div>
  242. </template>
  243. <template v-if="showMidScatterChart">
  244. <!-- 中间代散点图表头 -->
  245. <h2 class="chart-header">中间代散点图</h2>
  246. <div class="chart-container">
  247. <VueEcharts :option="ecMidScatterOption" ref="ecMidScatterOptionRef" />
  248. </div>
  249. </template>
  250. <template v-if="showFinalScatterChart">
  251. <!-- 最终代散点图表头 -->
  252. <h2 class="chart-header">最终代散点图</h2>
  253. <div class="chart-container">
  254. <VueEcharts :option="ecFinalScatterOption" ref="ecFinalScatterOptionRef" />
  255. </div>
  256. </template>
  257. </div>
  258. </template>
  259. <style scoped>
  260. .container {
  261. display: flex;
  262. flex-direction: column;
  263. align-items: center;
  264. justify-content: center;
  265. width: 100%;
  266. height: 100%;
  267. gap: 20px;
  268. }
  269. .chart-header {
  270. font-size: 18px;
  271. font-weight: bold;
  272. margin-bottom: 10px;
  273. }
  274. .sub-title {
  275. font-size: 14px;
  276. font-weight: 700;
  277. }
  278. .chart-container {
  279. width: 85%;
  280. height: 450px;
  281. margin: 0 auto;
  282. margin-bottom: 20px;
  283. }
  284. .VueEcharts {
  285. width: 100%;
  286. height: 100%;
  287. margin: 0 10px;
  288. }
  289. </style>