mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 09:27:07 +00:00
Add Vue components for 4D comparisons
This commit is contained in:
@@ -0,0 +1,160 @@
|
|||||||
|
<template>
|
||||||
|
<v-card v-if="comparison" class="ma-1">
|
||||||
|
<v-card-title>Comparison Summary: Baseline {{ baseline.pid }} vs Monitor {{ monitor.pid }}</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<h3>Deviation Statistics</h3>
|
||||||
|
<v-simple-table dense>
|
||||||
|
<template v-slot:default>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Metric</th>
|
||||||
|
<th>X (m)</th>
|
||||||
|
<th>Y (m)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Mean (μ)</td>
|
||||||
|
<td>{{ comparison['μ'][0].toFixed(3) }}</td>
|
||||||
|
<td>{{ comparison['μ'][1].toFixed(3) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Std Dev (σ)</td>
|
||||||
|
<td>{{ comparison['σ'][0].toFixed(3) }}</td>
|
||||||
|
<td>{{ comparison['σ'][1].toFixed(3) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>RMS</td>
|
||||||
|
<td>{{ comparison.rms[0].toFixed(3) }}</td>
|
||||||
|
<td>{{ comparison.rms[1].toFixed(3) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</template>
|
||||||
|
</v-simple-table>
|
||||||
|
|
||||||
|
<h3 class="mt-4">Counts</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Common Points: {{ comparison.common }}</li>
|
||||||
|
<li>Comparison Length: {{ comparison.length }}</li>
|
||||||
|
<li>Baseline Length: {{ comparison.baselineLength }} (Unique: {{ comparison.baselineUniqueLength }})</li>
|
||||||
|
<li>Monitor Length: {{ comparison.monitorLength }} (Unique: {{ comparison.monitorUniqueLength }})</li>
|
||||||
|
<li>Compared Points: {{ comparison.compared }}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 class="mt-4">Other Metrics</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Anisotropy: {{ comparison.anisotropy.toFixed(2) }}</li>
|
||||||
|
<li title="Relative to I-axis positive direction">Primary Direction: {{ (comparison.primaryDirection * 180 / Math.PI).toFixed(2) }}°</li>
|
||||||
|
<li>Timestamp: {{ new Date(comparison.tstamp).toLocaleString() }}</li>
|
||||||
|
</ul>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<h3>Error Ellipse</h3>
|
||||||
|
<svg width="300" height="300" style="border: 1px solid #ccc;">
|
||||||
|
<g :transform="`translate(150, 150) scale(${ellipseScale})`">
|
||||||
|
<line x1="0" y1="-150" x2="0" y2="150" stroke="lightgray" stroke-dasharray="5,5"/>
|
||||||
|
<line x1="-150" y1="0" x2="150" y2="0" stroke="lightgray" stroke-dasharray="5,5"/>
|
||||||
|
<ellipse
|
||||||
|
:rx="Math.sqrt(comparison.eigenvalues[0])"
|
||||||
|
:ry="Math.sqrt(comparison.eigenvalues[1])"
|
||||||
|
:transform="`rotate(${ellipseAngle})`"
|
||||||
|
fill="none"
|
||||||
|
stroke="blue"
|
||||||
|
stroke-width="2"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
:x1="0"
|
||||||
|
:y1="0"
|
||||||
|
:x2="Math.sqrt(comparison.eigenvalues[0]) * Math.cos(ellipseRad)"
|
||||||
|
:y2="Math.sqrt(comparison.eigenvalues[0]) * Math.sin(ellipseRad)"
|
||||||
|
stroke="red"
|
||||||
|
stroke-width="2"
|
||||||
|
arrow-end="classic-wide-long"
|
||||||
|
/>
|
||||||
|
<line
|
||||||
|
:x1="0"
|
||||||
|
:y1="0"
|
||||||
|
:x2="Math.sqrt(comparison.eigenvalues[1]) * Math.cos(ellipseRad + Math.PI / 2)"
|
||||||
|
:y2="Math.sqrt(comparison.eigenvalues[1]) * Math.sin(ellipseRad + Math.PI / 2)"
|
||||||
|
stroke="green"
|
||||||
|
stroke-width="2"
|
||||||
|
arrow-end="classic-wide-long"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<p class="text-caption">Ellipse scaled for visibility (factor: {{ ellipseScale.toFixed(1) }}). Axes represent sqrt(eigenvalues).</p>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "DougalGroupComparisonSummary",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
baseline: { type: Object, required: true },
|
||||||
|
monitor: { type: Object, required: true },
|
||||||
|
comparison: { type: Object, required: true }
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
ellipseAngle () {
|
||||||
|
if (!this.comparison) return 0;
|
||||||
|
const ev = this.comparison.eigenvectors[0];
|
||||||
|
return Math.atan2(ev[1], ev[0]) * 180 / Math.PI;
|
||||||
|
},
|
||||||
|
|
||||||
|
ellipseRad () {
|
||||||
|
return this.ellipseAngle * Math.PI / 180;
|
||||||
|
},
|
||||||
|
|
||||||
|
ellipseRx () {
|
||||||
|
if (!this.comparison) return 0;
|
||||||
|
return Math.sqrt(this.comparison.eigenvalues[0]) * this.ellipseScale;
|
||||||
|
},
|
||||||
|
|
||||||
|
ellipseRy () {
|
||||||
|
if (!this.comparison) return 0;
|
||||||
|
return Math.sqrt(this.comparison.eigenvalues[1]) * this.ellipseScale;
|
||||||
|
},
|
||||||
|
|
||||||
|
ellipseScale () {
|
||||||
|
if (!this.comparison) return 1;
|
||||||
|
const maxSigma = Math.max(
|
||||||
|
Math.sqrt(this.comparison.eigenvalues[0]),
|
||||||
|
Math.sqrt(this.comparison.eigenvalues[1])
|
||||||
|
);
|
||||||
|
const maxMu = Math.max(
|
||||||
|
Math.abs(this.comparison['μ'][0]),
|
||||||
|
Math.abs(this.comparison['μ'][1])
|
||||||
|
);
|
||||||
|
const maxExtent = maxMu + 3 * maxSigma;
|
||||||
|
return 100 / maxExtent; // Adjust scale to fit within ~200 pixels diameter
|
||||||
|
},
|
||||||
|
|
||||||
|
meanX () {
|
||||||
|
return this.comparison ? this.comparison['μ'][0] : 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
meanY () {
|
||||||
|
return this.comparison ? this.comparison['μ'][1] : 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
ellipseViewBox () {
|
||||||
|
return '-150 -150 300 300';
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<v-card class="ma-1">
|
||||||
|
<v-card-title>Group Repeatability Summary</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<p>Inline standard deviation (σ_x) for each baseline-monitor pair. Lower values indicate better repeatability. Colors range from green (best) to red (worst).</p>
|
||||||
|
<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
|
||||||
|
:style="{ backgroundColor: getSigmaXColor(baselineProject.pid, monitorProject.pid), color: 'white', textAlign: 'center', padding: '4px' }"
|
||||||
|
v-bind="attrs"
|
||||||
|
v-on="on"
|
||||||
|
@click="emitInput(baselineProject, monitorProject)"
|
||||||
|
>
|
||||||
|
{{ formatSigmaX(baselineProject.pid, monitorProject.pid) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span v-if="getComp(baselineProject.pid, monitorProject.pid)">
|
||||||
|
<div>σ_x: {{ getComp(baselineProject.pid, monitorProject.pid).meta['σ'][0].toFixed(2) }} m</div>
|
||||||
|
<div>σ_y: {{ getComp(baselineProject.pid, monitorProject.pid).meta['σ'][1].toFixed(2) }} m</div>
|
||||||
|
<div>Anisotropy: {{ getComp(baselineProject.pid, monitorProject.pid).meta.anisotropy.toFixed(0) }}</div>
|
||||||
|
<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]));
|
||||||
|
},
|
||||||
|
minSigmaX () {
|
||||||
|
if (!this.comparisons.length) return 0;
|
||||||
|
return Math.min(...this.comparisons.map(c => c.meta['σ'][0]));
|
||||||
|
},
|
||||||
|
maxSigmaX () {
|
||||||
|
if (!this.comparisons.length) return 0;
|
||||||
|
return Math.max(...this.comparisons.map(c => c.meta['σ'][0]));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getComp (basePid, monPid) {
|
||||||
|
return this.compMap.get(`${basePid}-${monPid}`);
|
||||||
|
},
|
||||||
|
getSigmaX (basePid, monPid) {
|
||||||
|
return this.getComp(basePid, monPid)?.meta?.['σ']?.[0] ?? null;
|
||||||
|
},
|
||||||
|
formatSigmaX (basePid, monPid) {
|
||||||
|
const val = this.getSigmaX(basePid, monPid);
|
||||||
|
return val !== null ? val.toFixed(1) : '';
|
||||||
|
},
|
||||||
|
getSigmaXColor (basePid, monPid) {
|
||||||
|
const val = this.getSigmaX(basePid, monPid);
|
||||||
|
if (val === null) return '';
|
||||||
|
const ratio = (val - this.minSigmaX) / (this.maxSigmaX - this.minSigmaX);
|
||||||
|
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>
|
||||||
Reference in New Issue
Block a user