diff --git a/frontend/src/components/PointInfo.vue b/frontend/src/components/PointInfo.vue index 7dc5ca2..87f64fa 100644 --- a/frontend/src/components/PointInfo.vue +++ b/frontend/src/components/PointInfo.vue @@ -55,7 +55,7 @@ {{ device.name }} </p> - <p class="battery" :style="{ color: batteryColor }" v-if="point.battery"> + <p class="battery" :style="{ color: batteryColor ?? 'initial' }" v-if="point.battery"> <font-awesome-icon :icon="batteryIconClass" /> <span>{{ point.battery }}%</span> </p> diff --git a/frontend/src/components/Timeline.vue b/frontend/src/components/Timeline.vue index 7cbb9fe..49cfecc 100644 --- a/frontend/src/components/Timeline.vue +++ b/frontend/src/components/Timeline.vue @@ -21,6 +21,13 @@ :title="(showMetrics.speed ? 'Hide' : 'Show') + ' speed'"> <font-awesome-icon icon="tachometer-alt" /> </button> + + <button @click="toggleMetric('battery')" + :class="{ selected: showMetrics.battery }" + :title="(showMetrics.battery ? 'Hide' : 'Show') + ' battery level'" + v-if="hasBatteryInfo"> + <font-awesome-icon icon="battery-full" /> + </button> </div> <div class="page-button-container"> @@ -128,12 +135,25 @@ export default { }) }, + hasBatteryInfo(): boolean { + return this.points.some((point: GPSPoint) => point.battery != null) + }, + + battery(): (number | null)[] { + return this.points + .map((point: GPSPoint) => point.battery ?? null) + }, + speed(): number[] { if (!this.points.length) { return [] } return this.points.map((point: GPSPoint, index: number) => { + if (point.speed != null) { + return point.speed + } + if (index === 0) { return 0 } @@ -185,6 +205,19 @@ export default { ) } + if (this.showMetrics.battery) { + datasets.push( + { + label: 'Battery (%)', + backgroundColor: '#0989f8', + borderColor: '#5959a8', + fill: false, + data: this.battery, + yAxisID: 'percentage', + } + ) + } + return { labels: this.points.map((point: GPSPoint) => point.timestamp), datasets: datasets, @@ -242,6 +275,31 @@ export default { } } + if (this.showMetrics.battery) { + let position = 'left' + if (yAxes.meters || yAxes.speed) { + position = 'right' + } + + yAxes.percentage = { + type: 'linear', + position: position, + display: true, + min: 0, + max: 100, + ticks: { + beginAtZero: true, + }, + title: { + display: true, + text: 'Percentage', + }, + grid: { + drawOnChartArea: false, + }, + } + } + const xTicks = {} as { min?: Date, max?: Date } if (this.points.length > 1) { xTicks.min = this.points[0].timestamp diff --git a/frontend/src/models/TimelineMetricsConfiguration.ts b/frontend/src/models/TimelineMetricsConfiguration.ts index 5794771..ef6d0ba 100644 --- a/frontend/src/models/TimelineMetricsConfiguration.ts +++ b/frontend/src/models/TimelineMetricsConfiguration.ts @@ -2,13 +2,14 @@ class TimelineMetricsConfiguration { public altitude: boolean = false; public distance: boolean = true; public speed: boolean = false; + public battery: boolean = false; constructor(data: any | null = null) { if (!data) { return; } - for (const key of ['altitude', 'distance', 'speed']) { + for (const key of ['altitude', 'distance', 'speed', 'battery']) { const value = String( data[key] ?? data['show' + key.charAt(0).toUpperCase() + key.slice(1)] ) @@ -39,13 +40,16 @@ class TimelineMetricsConfiguration { case 'speed': this.speed = !this.speed; break; + case 'battery': + this.battery = !this.battery; + break; default: throw new TypeError(`Invalid timeline metric: ${metric}`); } } toQuery(): Record<string, string> { - return ['altitude', 'distance', 'speed'].reduce((acc: Record<string, string>, key: string) => { + return ['altitude', 'distance', 'speed', 'battery'].reduce((acc: Record<string, string>, key: string) => { acc['show' + key.charAt(0).toUpperCase() + key.slice(1)] = String((this as any)[key]); return acc; }, {});