feat: GripAnalysisView — car grip envelope visualization
- New GripAnalysisView with 4 scatter charts: 1. Grip Envelope (peak G vs speed: lateral/accel/brake) 2. Grip Circle (longitudinal vs lateral G scatter) 3. Cornering Grip vs Speed 4. Steering Response (steer angle vs lateral G) - Summary stats: max lateral/accel/brake/combined G, peak lateral speed - GripAnalysis types + type guard - car_grip use case in SessionDetailView (quick stats + view button) - Route /analysis/grip/:id - Global .grid-4 CSS utility
This commit is contained in:
parent
c6bcab486c
commit
43ffedbf78
6 changed files with 392 additions and 6 deletions
|
|
@ -2,8 +2,8 @@
|
|||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useSessionsStore } from '@/stores/sessions'
|
||||
import type { ShiftPointAnalysis, TrackMapAnalysis } from '@/types'
|
||||
import { isShiftPointAnalysis, isTrackMapAnalysis } from '@/utils/analysis'
|
||||
import type { ShiftPointAnalysis, CarGripAnalysis, TrackMapAnalysis } from '@/types'
|
||||
import { isShiftPointAnalysis, isCarGripAnalysis, isTrackMapAnalysis } from '@/utils/analysis'
|
||||
import * as api from '@/api/client'
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -56,6 +56,14 @@ const trackAnalysis = computed((): TrackMapAnalysis | null => {
|
|||
return null
|
||||
})
|
||||
|
||||
const gripAnalysis = computed((): CarGripAnalysis | null => {
|
||||
if (!analysisData.value) return null
|
||||
if (isCarGripAnalysis(analysisData.value)) {
|
||||
return analysisData.value
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await store.fetchSession(sessionId.value)
|
||||
})
|
||||
|
|
@ -101,6 +109,8 @@ function viewAnalysis() {
|
|||
if (!session.value) return
|
||||
if (session.value.use_case === 'shift_points') {
|
||||
router.push({ name: 'shift-points', params: { id: session.value.id } })
|
||||
} else if (session.value.use_case === 'car_grip') {
|
||||
router.push({ name: 'grip-analysis', params: { id: session.value.id } })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -339,8 +349,28 @@ async function handleBuildTrack() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick stats for car grip -->
|
||||
<div v-if="gripAnalysis" class="analysis-stats grid grid-4 mb-2">
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Max Lateral G</span>
|
||||
<span class="stat-value mono">{{ gripAnalysis.summary.max_lateral_g.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Max Accel G</span>
|
||||
<span class="stat-value mono">{{ gripAnalysis.summary.max_accel_g.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Max Brake G</span>
|
||||
<span class="stat-value mono">{{ gripAnalysis.summary.max_brake_g.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">Max Combined G</span>
|
||||
<span class="stat-value mono">{{ gripAnalysis.summary.max_combined_g.toFixed(2) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button v-if="session.use_case === 'shift_points'" class="btn btn-primary" @click="viewAnalysis">
|
||||
<button v-if="session.use_case === 'shift_points' || session.use_case === 'car_grip'" class="btn btn-primary" @click="viewAnalysis">
|
||||
📊 View Full Analysis
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click="handleReAnalyze">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue