mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 11:37:08 +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