add inference result
This commit is contained in:
parent
5c4a8b7abf
commit
3c86c1e241
31
package-lock.json
generated
31
package-lock.json
generated
@ -4580,6 +4580,22 @@
|
|||||||
"safer-buffer": "^2.1.0"
|
"safer-buffer": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"echarts": {
|
||||||
|
"version": "5.5.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
|
||||||
|
"integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "2.3.0",
|
||||||
|
"zrender": "5.6.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"editorconfig": {
|
"editorconfig": {
|
||||||
"version": "0.15.3",
|
"version": "0.15.3",
|
||||||
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz",
|
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz",
|
||||||
@ -17562,6 +17578,21 @@
|
|||||||
"buffer-crc32": "~0.2.3",
|
"buffer-crc32": "~0.2.3",
|
||||||
"fd-slicer": "~1.1.0"
|
"fd-slicer": "~1.1.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"zrender": {
|
||||||
|
"version": "5.6.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",
|
||||||
|
"integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "2.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.26.1",
|
"axios": "^0.26.1",
|
||||||
|
"echarts": "^5.5.1",
|
||||||
"video.js": "^7.19.2",
|
"video.js": "^7.19.2",
|
||||||
"videojs-contrib-hls": "^5.15.0",
|
"videojs-contrib-hls": "^5.15.0",
|
||||||
"view-design": "^4.7.0",
|
"view-design": "^4.7.0",
|
||||||
|
@ -80,16 +80,19 @@
|
|||||||
<div class="layout-logo">
|
<div class="layout-logo">
|
||||||
<img :src="logo" alt="Logo" style="width: 60px; margin-right: 20px;" />{{ project_name }}</div>
|
<img :src="logo" alt="Logo" style="width: 60px; margin-right: 20px;" />{{ project_name }}</div>
|
||||||
</Menu>
|
</Menu>
|
||||||
<Menu mode="horizontal" active-name="1" @on-select="handleSubMenuSelect" class="sub_menu">
|
<Menu mode="horizontal" active-name="3" @on-select="handleSubMenuSelect" class="sub_menu">
|
||||||
<div class="layout-assistant">
|
<div class="layout-assistant">
|
||||||
<Menu-item name="1">
|
<Menu-item name="1">
|
||||||
<Icon type="md-pie" size="16" />{{ sub_navi_name[0] }}
|
<Icon type="md-pie" size="16" />{{ sub_navi_name[0] }}
|
||||||
</Menu-item>
|
</Menu-item>
|
||||||
<Menu-item name="2">
|
<Menu-item name="2">
|
||||||
<Icon type="md-stats" size="16" />{{ sub_navi_name[1] }}
|
<Icon type="ios-image" size="16" />{{ sub_navi_name[1] }}
|
||||||
</Menu-item>
|
</Menu-item>
|
||||||
<Menu-item name="3">
|
<Menu-item name="3">
|
||||||
<Icon type="md-barcode" size="16" />{{ sub_navi_name[2] }}
|
<Icon type="md-stats" size="16" />{{ sub_navi_name[2] }}
|
||||||
|
</Menu-item>
|
||||||
|
<Menu-item name="4">
|
||||||
|
<Icon type="md-barcode" size="16" />{{ sub_navi_name[3] }}
|
||||||
</Menu-item>
|
</Menu-item>
|
||||||
</div>
|
</div>
|
||||||
</Menu>
|
</Menu>
|
||||||
@ -112,17 +115,17 @@
|
|||||||
import SequenceVisualize from '@/components/content/SeqVisualize.vue'
|
import SequenceVisualize from '@/components/content/SeqVisualize.vue'
|
||||||
import SeqInferenceResultVisualize from '@/components/content/SeqInferenceResultVisualize.vue';
|
import SeqInferenceResultVisualize from '@/components/content/SeqInferenceResultVisualize.vue';
|
||||||
import OnlineInference from '@/components/content/OnlineInference.vue';
|
import OnlineInference from '@/components/content/OnlineInference.vue';
|
||||||
|
import InferenceResultAnalysis from '@/components/content/InferenceResultAnalysis.vue';
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
project_name: "Next Best View for 3D Reconstruction",
|
project_name: "Next Best View for 3D Reconstruction",
|
||||||
logo: require('@/assets/white_logo.png'),
|
logo: require('@/assets/white_logo.png'),
|
||||||
|
|
||||||
sub_navi_name: ["Sequence Visualization", "Inference Result Visualization", "Online Inference"],
|
sub_navi_name: ["Sequence Visualization", "Inference Result Visualization", "Inference Result Analysis","Online Inference"],
|
||||||
curr_navi_idx: 0,
|
curr_navi_idx: 0,
|
||||||
curr_sub_navi_idx: 0,
|
curr_sub_navi_idx: 2,
|
||||||
components: [SequenceVisualize, SeqInferenceResultVisualize, OnlineInference],
|
components: [SequenceVisualize, SeqInferenceResultVisualize, InferenceResultAnalysis, OnlineInference],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
108
src/components/content/InferenceResultAnalysis.vue
Normal file
108
src/components/content/InferenceResultAnalysis.vue
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<div style="padding: 40px;">
|
||||||
|
<div style="display: flex; justify-content: center;">
|
||||||
|
<h1>Inference Result Analysis</h1>
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<StatResultAnalysis
|
||||||
|
v-for="(result, name) in results"
|
||||||
|
:key="name"
|
||||||
|
:result="result"
|
||||||
|
:name="name"
|
||||||
|
/>
|
||||||
|
<Divider/>
|
||||||
|
<Upload multiple type="drag" action="" accept=".json" :before-upload="beforeUploadInferenceResultFile"
|
||||||
|
style="width: 100%; position: relative; margin-top:10px">
|
||||||
|
<div style="padding: 20px 0">
|
||||||
|
<Icon type="ios-cloud-upload" size="52" style="color: #3399ff" v-if="!loading">
|
||||||
|
</Icon>
|
||||||
|
<p v-if="!loading">Select or drag the inference statistical result file(.json) here to upload
|
||||||
|
</p>
|
||||||
|
<div class="demo-spin-container" v-if="loading">
|
||||||
|
<Spin fix size="large"></Spin>
|
||||||
|
</div>
|
||||||
|
<p v-if="loading">Waiting for server response...</p>
|
||||||
|
</div>
|
||||||
|
</Upload>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import StatResultAnalysis from './cards/StatResultAnalysis.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "InferenceResultAnalysis",
|
||||||
|
components: {
|
||||||
|
StatResultAnalysis
|
||||||
|
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
results: {},
|
||||||
|
loading: false,
|
||||||
|
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
beforeUploadInferenceResultFile(file) {
|
||||||
|
this.loading = true;
|
||||||
|
const isJson = file.name.endsWith('.json');
|
||||||
|
var result_name = file.name;
|
||||||
|
if (!isJson) {
|
||||||
|
this.$Notice.warning({
|
||||||
|
title: 'File format error',
|
||||||
|
desc: 'You can only upload a .json file, current file: ' + file.name
|
||||||
|
});
|
||||||
|
this.loading = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (event) => {
|
||||||
|
try {
|
||||||
|
const jsonObject = JSON.parse(event.target.result);
|
||||||
|
var repeat = 0;
|
||||||
|
while (this.results[result_name]) {
|
||||||
|
result_name = file.name + `(${repeat})`;
|
||||||
|
repeat++;
|
||||||
|
}
|
||||||
|
this.results[result_name] = jsonObject;
|
||||||
|
console.log(this.results);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
this.$Notice.error({
|
||||||
|
title: 'File parse error',
|
||||||
|
desc: `The JSON file(${file.name}) could not be parsed.`
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
384
src/components/content/cards/StatResultAnalysis.vue
Normal file
384
src/components/content/cards/StatResultAnalysis.vue
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
<template>
|
||||||
|
<Card style="width: 100%;">
|
||||||
|
<p slot="title" style="font-size: 18px; font-weight: bold; color: #464c5b;">
|
||||||
|
<Icon type="md-stats" size="20" style="color: #464c5b;" />
|
||||||
|
Inference Result: <span style="color: green;">{{ name }}</span>
|
||||||
|
</p>
|
||||||
|
<Alert show-icon><span style="font-weight: bold;">Success Rate(SR)</span> = Predicted Final Coverage Rate / Max
|
||||||
|
GT Final Coverage Rate</Alert>
|
||||||
|
<Row>
|
||||||
|
<!-- 左侧展示条形图 -->
|
||||||
|
<Col span="12">
|
||||||
|
<Card>
|
||||||
|
<p slot="title" style="font-size: 16px; font-weight: bold;">Success Rate Histogram</p>
|
||||||
|
<div ref="histogramChart" style="height: 600px; width: 100%;"></div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<!-- 右侧展示饼状图 -->
|
||||||
|
<Col span="12">
|
||||||
|
<Card>
|
||||||
|
<p slot="title" style="font-size: 16px; font-weight: bold;">Success Rate Pie Chart</p>
|
||||||
|
<div ref="pieChart" style="height: 600px; width: 100%;"></div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style="margin-top: 10px;">
|
||||||
|
<Card style="width: 100%; height: 600px;">
|
||||||
|
<p slot="title" style="font-size: 16px; font-weight: bold;">Single Object Analysis</p>
|
||||||
|
<Row>
|
||||||
|
<Col span="8">
|
||||||
|
<Row style="margin-bottom: 10px; margin-left: 10px; margin-right: 10px;">
|
||||||
|
<Col span="8">
|
||||||
|
<p style="font-size: 16px; color: #464c5b; font-weight: bold;">Select Scene:</p>
|
||||||
|
</Col>
|
||||||
|
<Col span="14">
|
||||||
|
<Select v-model="sceneName" style="width: 100%;" @on-change="handleSceneChange"
|
||||||
|
placeholder="please select..." filterable>
|
||||||
|
<Option v-for="(value, key) in result" :value="key" :key="key" :label="key"><span>{{ key
|
||||||
|
}}</span>
|
||||||
|
<span style="float:right;color:#ccc">(SR=<span
|
||||||
|
:style="{ color: getSuccessRateColor(result[key]['success_rate']) }">{{
|
||||||
|
(result[key]["success_rate"] * 100).toFixed(2) }}%</span>)</span>
|
||||||
|
</Option>
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
<Col span="1">
|
||||||
|
<Divider type="vertical" style="height: 100%;" />
|
||||||
|
</Col>
|
||||||
|
<Col span="14">
|
||||||
|
|
||||||
|
<span style="font-weight: bold;">Max Coverage Rate: <span style="color: green;">{{ result[sceneName]
|
||||||
|
? (result[sceneName]["max_coverage_rate"] * 100).toFixed(2) : 0 }}%</span></span>
|
||||||
|
<Divider type="vertical" style="height: 100%;" />
|
||||||
|
<span style="font-weight: bold;">Pred Coverage Rate: <span style="color: green;">{{
|
||||||
|
result[sceneName]
|
||||||
|
? (result[sceneName]["pred_max_coverage_rate"]*100).toFixed(2) : 0 }}%</span></span>
|
||||||
|
<Divider type="vertical" style="height: 100%;" />
|
||||||
|
<span style="font-weight: bold;">Success Rate: <span style="color: green;">{{ result[sceneName] ?
|
||||||
|
(result[sceneName]["success_rate"] * 100).toFixed(2) : 0 }}%</span></span>
|
||||||
|
<Divider type="vertical" style="height: 100%;" />
|
||||||
|
<span style="font-weight: bold;">Pred Length/GT Length:
|
||||||
|
<span style="color: red;">{{ result[sceneName] ? (result[sceneName]["pred_seq_len"]) : 0
|
||||||
|
}}</span>/<span style="color: green;">?</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Divider />
|
||||||
|
<Row>
|
||||||
|
<Card style="height: 400px; width: 100%;">
|
||||||
|
<p slot="title" style="font-size: 16px; font-weight: bold;">Prediction Curve</p>
|
||||||
|
<div ref="predictionCurve" style="height: 350px; width: 100%;"></div>
|
||||||
|
</Card>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import { Divider } from 'view-design';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "StatResultAnalysis",
|
||||||
|
props: {
|
||||||
|
result: Object,
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log(this.result);
|
||||||
|
this.drawCharts();
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
sceneName: null,
|
||||||
|
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getSuccessRateColor(rate) {
|
||||||
|
if (rate >= 0.7) {
|
||||||
|
return 'green';
|
||||||
|
} else if (rate >= 0.4) {
|
||||||
|
return 'orange';
|
||||||
|
} else {
|
||||||
|
return 'red';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSceneChange(val) {
|
||||||
|
if (val) {
|
||||||
|
this.sceneName = val;
|
||||||
|
console.log("Selected scene name:", this.sceneName);
|
||||||
|
this.drawLines(); // 选择场景后绘制折线图
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drawLines() {
|
||||||
|
if (!this.sceneName || !this.result[this.sceneName]) return;
|
||||||
|
|
||||||
|
const coverageRateSeq = this.result[this.sceneName]["coverage_rate_seq"];
|
||||||
|
const maxCoverageRate = this.result[this.sceneName]["max_coverage_rate"];
|
||||||
|
const successRateSeq = coverageRateSeq.map(rate => (rate * 100 / maxCoverageRate).toFixed(2)); // 计算成功率序列
|
||||||
|
|
||||||
|
// 初始化折线图
|
||||||
|
const predictionCurve = echarts.init(this.$refs.predictionCurve);
|
||||||
|
const lineOption = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
formatter: (params) => {
|
||||||
|
let tooltip = params[0].name + '<br>';
|
||||||
|
params.forEach(item => {
|
||||||
|
tooltip += `${item.seriesName}: ${item.data}%<br>`;
|
||||||
|
});
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: coverageRateSeq.map((_, index) => `${index + 1}`), // x轴标签
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: 'Coverage Rate',
|
||||||
|
axisLabel: {
|
||||||
|
formatter: '{value}%'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Coverage Rate',
|
||||||
|
type: 'line',
|
||||||
|
data: coverageRateSeq.map(rate => (rate * 100).toFixed(2)),
|
||||||
|
itemStyle: {
|
||||||
|
color: '#3399ff'
|
||||||
|
},
|
||||||
|
smooth: false // 曲线平滑
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Success Rate',
|
||||||
|
type: 'line',
|
||||||
|
data: successRateSeq,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#ff5733' // 另一种颜色
|
||||||
|
},
|
||||||
|
smooth: false // 曲线平滑
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Max Coverage Rate',
|
||||||
|
type: 'line',
|
||||||
|
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed' // 设置为虚线
|
||||||
|
},
|
||||||
|
markLine: {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: 'GT Max Coverage Rate',
|
||||||
|
yAxis: (maxCoverageRate * 100).toFixed(2),
|
||||||
|
label: {
|
||||||
|
formatter: '{c}%' // 在标记线上显示百分号
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
};
|
||||||
|
predictionCurve.setOption(lineOption);
|
||||||
|
},
|
||||||
|
drawCharts() {
|
||||||
|
// 提取成功率数据
|
||||||
|
const successRates = Object.values(this.result).map(item => item.success_rate);
|
||||||
|
|
||||||
|
// 计算平均数
|
||||||
|
const avgSuccessRate = parseFloat((successRates.reduce((sum, rate) => sum + rate, 0) / successRates.length).toFixed(4));
|
||||||
|
console.log("avg", avgSuccessRate);
|
||||||
|
|
||||||
|
// 计算中位数
|
||||||
|
const sortedRates = [...successRates].sort((a, b) => a - b);
|
||||||
|
const mid = Math.floor(sortedRates.length / 2);
|
||||||
|
const medianSuccessRate = parseFloat((sortedRates.length % 2 !== 0 ? sortedRates[mid] : ((sortedRates[mid - 1] + sortedRates[mid]) / 2)).toFixed(4));
|
||||||
|
|
||||||
|
// 定义区间
|
||||||
|
const intervals = ['0-10%', '10-20%', '20-30%', '30-40%', '40-50%', '50-60%', '60-70%', '70-80%', '80-90%', '90-100%', '>=100%'];
|
||||||
|
const intervalCounts = new Array(intervals.length).fill(0);
|
||||||
|
|
||||||
|
// 将成功率分类到对应的区间
|
||||||
|
successRates.forEach(rate => {
|
||||||
|
let index = Math.min(Math.floor(rate * 10), 10); // 大于100%的情况归类为最后一项
|
||||||
|
intervalCounts[index]++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成条形图数据
|
||||||
|
const histogramData = intervalCounts.map((count, index) => ({
|
||||||
|
name: intervals[index],
|
||||||
|
value: count
|
||||||
|
}));
|
||||||
|
|
||||||
|
const histogramChart = echarts.init(this.$refs.histogramChart);
|
||||||
|
const seriesData = [
|
||||||
|
{
|
||||||
|
name: 'Number of Objects',
|
||||||
|
type: 'bar',
|
||||||
|
data: histogramData.map(item => item.value),
|
||||||
|
itemStyle: {
|
||||||
|
color: '#3399ff'
|
||||||
|
},
|
||||||
|
markPoint: {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: 'Median',
|
||||||
|
coord: [medianSuccessRate * 10 - 1, histogramData[Math.floor(medianSuccessRate * 10)].value],
|
||||||
|
label: {
|
||||||
|
formatter: `Median: \n${(medianSuccessRate * 100).toFixed(2)}%`,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: 'green'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Avg',
|
||||||
|
coord: [avgSuccessRate * 10 - 1, histogramData[Math.floor(avgSuccessRate * 10)].value],
|
||||||
|
label: {
|
||||||
|
formatter: `Average: \n${(avgSuccessRate * 100).toFixed(2)}%`,
|
||||||
|
position: 'top'
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: 'orange'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const histogramOption = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: intervals,
|
||||||
|
name: 'SR (%)'
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
name: 'Number of Objects'
|
||||||
|
},
|
||||||
|
series: seriesData
|
||||||
|
};
|
||||||
|
histogramChart.setOption(histogramOption);
|
||||||
|
|
||||||
|
const pieIntervals = ['0-40%', '40-70%', '70-100%', '>=100%'];
|
||||||
|
const pieIntervalCounts = new Array(intervals.length).fill(0);
|
||||||
|
|
||||||
|
successRates.forEach(rate => {
|
||||||
|
if (rate <= 0.4) {
|
||||||
|
pieIntervalCounts[0]++;
|
||||||
|
} else if (rate <= 0.7) {
|
||||||
|
pieIntervalCounts[1]++;
|
||||||
|
} else if (rate < 1.0) {
|
||||||
|
pieIntervalCounts[2]++;
|
||||||
|
} else {
|
||||||
|
pieIntervalCounts[3]++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成内环饼图数据
|
||||||
|
const innerPieData = pieIntervals.map((interval, index) => ({
|
||||||
|
name: interval,
|
||||||
|
value: pieIntervalCounts[index]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 生成外环饼图数据
|
||||||
|
const outerPieData = histogramData.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 初始化饼状图
|
||||||
|
const pieChart = echarts.init(this.$refs.pieChart);
|
||||||
|
const pieOption = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: outerPieData.map(item => item.name) // 使用内环数据的名称
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'Success Rate',
|
||||||
|
type: 'pie',
|
||||||
|
selectedMode: 'single',
|
||||||
|
radius: [0, '30%'],
|
||||||
|
label: {
|
||||||
|
position: 'inner',
|
||||||
|
fontSize: 14
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
data: innerPieData // 内环数据
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Success Rate',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['45%', '60%'],
|
||||||
|
labelLine: {
|
||||||
|
length: 30
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}:}{c} {per|{d}%} ',
|
||||||
|
backgroundColor: '#F6F8FC',
|
||||||
|
borderColor: '#8C8D8E',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 4,
|
||||||
|
rich: {
|
||||||
|
a: {
|
||||||
|
color: '#6E7079',
|
||||||
|
lineHeight: 22,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
hr: {
|
||||||
|
borderColor: '#8C8D8E',
|
||||||
|
width: '100%',
|
||||||
|
borderWidth: 1,
|
||||||
|
height: 0
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
color: '#4C5058',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
lineHeight: 33
|
||||||
|
},
|
||||||
|
per: {
|
||||||
|
color: '#fff',
|
||||||
|
backgroundColor: '#4C5058',
|
||||||
|
padding: [3, 4],
|
||||||
|
borderRadius: 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: outerPieData // 外环数据
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
pieChart.setOption(pieOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 添加必要的样式 */
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user