2025-08-18 14:13:40 +02:00
|
|
|
|
<template>
|
|
|
|
|
|
<v-card class="ma-1">
|
|
|
|
|
|
<v-card-title>Group Repeatability Summary</v-card-title>
|
|
|
|
|
|
<v-card-text>
|
2025-08-19 19:28:19 +02:00
|
|
|
|
<p>Error ellipse area for each baseline-monitor pair. Lower values indicate better repeatability. Colors range from green (best) to red (worst).</p>
|
2025-08-18 14:13:40 +02:00
|
|
|
|
<v-simple-table dense>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>Baseline \ Monitor</th>
|
|
|
|
|
|
<th v-for="project in projects" :key="project.pid">{{ project.pid }}</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="(baselineProject, rowIndex) in projects" :key="baselineProject.pid">
|
|
|
|
|
|
<td>{{ baselineProject.pid }}</td>
|
|
|
|
|
|
<td v-for="(monitorProject, colIndex) in projects" :key="monitorProject.pid">
|
|
|
|
|
|
<v-tooltip v-if="colIndex > rowIndex" top>
|
|
|
|
|
|
<template v-slot:activator="{ on, attrs }">
|
|
|
|
|
|
<div
|
2025-08-19 19:28:19 +02:00
|
|
|
|
:style="{ backgroundColor: getEllipseAreaColor(baselineProject.pid, monitorProject.pid), color: 'white', textAlign: 'center', padding: '4px' }"
|
2025-08-18 14:13:40 +02:00
|
|
|
|
v-bind="attrs"
|
|
|
|
|
|
v-on="on"
|
|
|
|
|
|
@click="emitInput(baselineProject, monitorProject)"
|
|
|
|
|
|
>
|
2025-08-19 19:28:19 +02:00
|
|
|
|
{{ formatEllipseArea(baselineProject.pid, monitorProject.pid) }}
|
2025-08-18 14:13:40 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<span v-if="getComp(baselineProject.pid, monitorProject.pid)">
|
2025-08-19 19:28:19 +02:00
|
|
|
|
<div>σ_i: {{ getComp(baselineProject.pid, monitorProject.pid).meta['σ'][0].toFixed(2) }} m</div>
|
|
|
|
|
|
<div>σ_j: {{ getComp(baselineProject.pid, monitorProject.pid).meta['σ'][1].toFixed(2) }} m</div>
|
2025-08-18 14:13:40 +02:00
|
|
|
|
<div>Anisotropy: {{ getComp(baselineProject.pid, monitorProject.pid).meta.anisotropy.toFixed(0) }}</div>
|
2025-08-19 19:28:19 +02:00
|
|
|
|
<div>Ellipse Area: {{ getEllipseArea(baselineProject.pid, monitorProject.pid).toFixed(2) }} m²</div>
|
2025-08-18 14:13:40 +02:00
|
|
|
|
<div>Primary Direction: {{ formatPrimaryDirection(getComp(baselineProject.pid, monitorProject.pid)) }}°</div>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</v-tooltip>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</v-simple-table>
|
|
|
|
|
|
</v-card-text>
|
|
|
|
|
|
</v-card>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'DougalGroupRepeatabilitySummary',
|
|
|
|
|
|
|
|
|
|
|
|
props: {
|
|
|
|
|
|
comparisons: {
|
|
|
|
|
|
type: Array,
|
|
|
|
|
|
required: true
|
|
|
|
|
|
},
|
|
|
|
|
|
projects: {
|
|
|
|
|
|
type: Array,
|
|
|
|
|
|
required: true
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
data () {
|
|
|
|
|
|
return {
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
computed: {
|
|
|
|
|
|
compMap () {
|
|
|
|
|
|
return new Map(this.comparisons.map(c => [`${c.baseline_pid}-${c.monitor_pid}`, c]));
|
|
|
|
|
|
},
|
2025-08-19 19:28:19 +02:00
|
|
|
|
minEllipseArea () {
|
2025-08-18 14:13:40 +02:00
|
|
|
|
if (!this.comparisons.length) return 0;
|
2025-08-19 19:28:19 +02:00
|
|
|
|
return Math.min(...this.comparisons.map(c => {
|
|
|
|
|
|
const a = Math.sqrt(c.meta.eigenvalues[0]);
|
|
|
|
|
|
const b = Math.sqrt(c.meta.eigenvalues[1]);
|
|
|
|
|
|
return Math.PI * a * b;
|
|
|
|
|
|
}));
|
2025-08-18 14:13:40 +02:00
|
|
|
|
},
|
2025-08-19 19:28:19 +02:00
|
|
|
|
maxEllipseArea () {
|
2025-08-18 14:13:40 +02:00
|
|
|
|
if (!this.comparisons.length) return 0;
|
2025-08-19 19:28:19 +02:00
|
|
|
|
return Math.max(...this.comparisons.map(c => {
|
|
|
|
|
|
const a = Math.sqrt(c.meta.eigenvalues[0]);
|
|
|
|
|
|
const b = Math.sqrt(c.meta.eigenvalues[1]);
|
|
|
|
|
|
return Math.PI * a * b;
|
|
|
|
|
|
}));
|
2025-08-18 14:13:40 +02:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
getComp (basePid, monPid) {
|
|
|
|
|
|
return this.compMap.get(`${basePid}-${monPid}`);
|
|
|
|
|
|
},
|
2025-08-19 19:28:19 +02:00
|
|
|
|
getEllipseArea (basePid, monPid) {
|
|
|
|
|
|
const comp = this.getComp(basePid, monPid);
|
|
|
|
|
|
if (!comp) return null;
|
|
|
|
|
|
const a = Math.sqrt(comp.meta.eigenvalues[0]);
|
|
|
|
|
|
const b = Math.sqrt(comp.meta.eigenvalues[1]);
|
|
|
|
|
|
return Math.PI * a * b;
|
2025-08-18 14:13:40 +02:00
|
|
|
|
},
|
2025-08-19 19:28:19 +02:00
|
|
|
|
formatEllipseArea (basePid, monPid) {
|
|
|
|
|
|
const val = this.getEllipseArea(basePid, monPid);
|
2025-08-18 14:13:40 +02:00
|
|
|
|
return val !== null ? val.toFixed(1) : '';
|
|
|
|
|
|
},
|
2025-08-19 19:28:19 +02:00
|
|
|
|
getEllipseAreaColor (basePid, monPid) {
|
|
|
|
|
|
const val = this.getEllipseArea(basePid, monPid);
|
2025-08-18 14:13:40 +02:00
|
|
|
|
if (val === null) return '';
|
2025-08-19 19:28:19 +02:00
|
|
|
|
const ratio = (val - this.minEllipseArea) / (this.maxEllipseArea - this.minEllipseArea);
|
2025-08-18 14:13:40 +02:00
|
|
|
|
const hue = (1 - ratio) * 120;
|
|
|
|
|
|
return `hsl(${hue}, 70%, 70%)`;
|
|
|
|
|
|
},
|
|
|
|
|
|
formatPrimaryDirection (comp) {
|
|
|
|
|
|
if (!comp) return '';
|
|
|
|
|
|
return (comp.meta.primaryDirection * 180 / Math.PI).toFixed(1);
|
|
|
|
|
|
},
|
|
|
|
|
|
emitInput (baselineProject, monitorProject) {
|
|
|
|
|
|
if (this.getComp(baselineProject.pid, monitorProject.pid)) {
|
|
|
|
|
|
this.$emit('input', baselineProject, monitorProject);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|