152 lines
3.3 KiB
Vue
152 lines
3.3 KiB
Vue
<script setup lang="ts">
|
|
import {computed} from 'vue'
|
|
|
|
const props = defineProps<{
|
|
history: any[]
|
|
currentAltitude: number | null
|
|
unit: string
|
|
}>()
|
|
|
|
const WIDTH = 320
|
|
const HEIGHT = 120
|
|
const PAD = {top: 10, right: 12, bottom: 24, left: 42}
|
|
|
|
const points = computed(() => {
|
|
const history = [...(props.history || [])]
|
|
.filter(p => p.ts != null && p.altitude != null && !p.live)
|
|
.sort((a, b) => Number(a.ts) - Number(b.ts))
|
|
|
|
if (props.currentAltitude != null && history.length) {
|
|
const last = history[history.length - 1]
|
|
history.push({
|
|
ts: Math.max(Number(last.ts) + 1, Math.floor(Date.now() / 1000)),
|
|
altitude: props.currentAltitude,
|
|
live: true,
|
|
})
|
|
}
|
|
|
|
return history
|
|
})
|
|
|
|
const graph = computed(() => {
|
|
const pts = points.value
|
|
if (pts.length < 2) return null
|
|
|
|
const minTs = Number(pts[0].ts)
|
|
const maxTs = Number(pts[pts.length - 1].ts)
|
|
const rawAltitudes = pts.map(p => Number(p.altitude))
|
|
const minAlt = Math.min(...rawAltitudes)
|
|
const maxAlt = Math.max(...rawAltitudes)
|
|
const altRange = Math.max(maxAlt - minAlt, 1)
|
|
const timeRange = Math.max(maxTs - minTs, 1)
|
|
|
|
const x = (ts: number) => PAD.left + ((ts - minTs) / timeRange) * (WIDTH - PAD.left - PAD.right)
|
|
const y = (alt: number) => PAD.top + (1 - (alt - minAlt) / altRange) * (HEIGHT - PAD.top - PAD.bottom)
|
|
|
|
const line = pts.map(p => `${x(Number(p.ts)).toFixed(1)},${y(Number(p.altitude)).toFixed(1)}`).join(' ')
|
|
const latest = pts[pts.length - 1]
|
|
const latestX = x(Number(latest.ts))
|
|
const latestY = y(Number(latest.altitude))
|
|
|
|
const formatTime = (ts: number) => {
|
|
const d = new Date(ts * 1000)
|
|
return d.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'})
|
|
}
|
|
|
|
return {
|
|
line,
|
|
latestX,
|
|
latestY,
|
|
minAlt,
|
|
maxAlt,
|
|
startTime: formatTime(minTs),
|
|
endTime: formatTime(maxTs),
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.altitude-graph {
|
|
padding: 0 16px 12px;
|
|
}
|
|
|
|
.altitude-graph-title {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
color: #888;
|
|
font-size: 11px;
|
|
font-weight: bold;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.altitude-graph svg {
|
|
display: block;
|
|
width: 100%;
|
|
height: 120px;
|
|
overflow: visible;
|
|
}
|
|
|
|
.altitude-graph text {
|
|
fill: #777;
|
|
font-family: monospace;
|
|
font-size: 9px;
|
|
}
|
|
</style>
|
|
|
|
<template>
|
|
<div v-if="graph" class="altitude-graph">
|
|
<div class="altitude-graph-title">
|
|
<span>ALTITUDE</span>
|
|
<span>{{ unit }}</span>
|
|
</div>
|
|
|
|
<svg :viewBox="`0 0 ${WIDTH} ${HEIGHT}`" preserveAspectRatio="none">
|
|
<line
|
|
:x1="PAD.left"
|
|
:y1="PAD.top"
|
|
:x2="PAD.left"
|
|
:y2="HEIGHT - PAD.bottom"
|
|
stroke="rgba(255,255,255,0.2)"
|
|
/>
|
|
|
|
<line
|
|
:x1="PAD.left"
|
|
:y1="HEIGHT - PAD.bottom"
|
|
:x2="WIDTH - PAD.right"
|
|
:y2="HEIGHT - PAD.bottom"
|
|
stroke="rgba(255,255,255,0.2)"
|
|
/>
|
|
|
|
<text :x="PAD.left - 5" :y="PAD.top + 4" text-anchor="end">
|
|
{{ graph.maxAlt }}
|
|
</text>
|
|
|
|
<text :x="PAD.left - 5" :y="HEIGHT - PAD.bottom + 4" text-anchor="end">
|
|
{{ graph.minAlt }}
|
|
</text>
|
|
|
|
<polyline
|
|
:points="graph.line"
|
|
fill="none"
|
|
stroke="#0f0"
|
|
stroke-width="2"
|
|
/>
|
|
|
|
<circle
|
|
:cx="graph.latestX"
|
|
:cy="graph.latestY"
|
|
r="3"
|
|
fill="#0f0"
|
|
/>
|
|
|
|
<text :x="PAD.left" :y="HEIGHT - 6">
|
|
{{ graph.startTime }}
|
|
</text>
|
|
|
|
<text :x="WIDTH - PAD.right" :y="HEIGHT - 6" text-anchor="end">
|
|
{{ graph.endTime }}
|
|
</text>
|
|
</svg>
|
|
</div>
|
|
</template>
|