Cleanup
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, onMounted} from 'vue';
|
import {ref, onMounted} from 'vue';
|
||||||
import {api} from '../../services/api';
|
import {api} from '../services/api';
|
||||||
import MetricRow from './MetricRow.vue';
|
import MetricRow from './MetricRow.vue';
|
||||||
|
|
||||||
const d = ref<any>(null);
|
const d = ref<any>(null);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { fromLonLat, toLonLat } from 'ol/proj'
|
|||||||
import { Style, Fill, Stroke, Circle as CircleStyle } from 'ol/style'
|
import { Style, Fill, Stroke, Circle as CircleStyle } from 'ol/style'
|
||||||
import 'ol/ol.css'
|
import 'ol/ol.css'
|
||||||
|
|
||||||
const current = ref([0, 0]);
|
const current = ref({latitude: 0, longitude: 0});
|
||||||
const props = defineProps<{ dark: boolean }>()
|
const props = defineProps<{ dark: boolean }>()
|
||||||
const mapEl = ref<HTMLDivElement>()
|
const mapEl = ref<HTMLDivElement>()
|
||||||
const showOverlays = ref(false)
|
const showOverlays = ref(false)
|
||||||
@@ -60,7 +60,7 @@ function buildWindSrc(lat: number, lon: number, zoom: number) {
|
|||||||
|
|
||||||
function syncWindSrc() {
|
function syncWindSrc() {
|
||||||
const view = map.getView()
|
const view = map.getView()
|
||||||
const center = toLonLat(view.getCenter()!)
|
const center: any = toLonLat(view.getCenter()!)
|
||||||
windSrc.value = buildWindSrc(center[1], center[0], view.getZoom() ?? 8)
|
windSrc.value = buildWindSrc(center[1], center[0], view.getZoom() ?? 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,160 +1,155 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, onMounted } from 'vue'
|
import {computed, ref, onMounted} from 'vue';
|
||||||
import { Line } from 'vue-chartjs'
|
import {Line} from 'vue-chartjs';
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS, CategoryScale, LinearScale, PointElement,
|
Chart as ChartJS, CategoryScale, LinearScale, PointElement,
|
||||||
LineElement, Tooltip, Legend, Filler, type ChartOptions
|
LineElement, Tooltip, Legend, Filler, type ChartOptions
|
||||||
} from 'chart.js'
|
} from 'chart.js';
|
||||||
import { METRICS, formatValue } from '../services/units'
|
import {METRICS, formatValue} from '../services/units';
|
||||||
import { fetchHistoricHourly } from '../services/weather'
|
import type {DataRow} from '../services/api';
|
||||||
import type { DataRow } from '../services/api'
|
import {api} from '../services/api';
|
||||||
import { api } from '../services/api'
|
|
||||||
|
|
||||||
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Tooltip, Legend, Filler)
|
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Tooltip, Legend, Filler);
|
||||||
|
|
||||||
const props = defineProps<{ metricKey: string; currentData: DataRow }>()
|
const props = defineProps<{metricKey: string; currentData: DataRow}>();
|
||||||
|
|
||||||
const historic = ref<DataRow[]>([])
|
const historic = ref<DataRow[]>([]);
|
||||||
const forecast = ref<DataRow[]>([])
|
const forecast = ref<DataRow[]>([]);
|
||||||
const loading = ref(true)
|
const loading = ref(true);
|
||||||
const meta = computed(() => METRICS[props.metricKey])
|
const meta = computed(() => METRICS[props.metricKey]);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const now = new Date()
|
const start = new Date(Date.now() - 24 * 3600000);
|
||||||
const start = new Date(now.getTime() - 24 * 3600000).toISOString()
|
const end = new Date(Date.now() + 48 * 3600000);
|
||||||
const end = new Date(now.getTime() + 48 * 3600000).toISOString()
|
const hourly = await api.hourly(start, end);
|
||||||
const [hist, fore] = await Promise.allSettled([
|
historic.value = hourly.filter((h: any) => new Date(h.time).getTime() <= Date.now());
|
||||||
fetchHistoricHourly(start, now.toISOString()),
|
forecast.value = hourly.filter((h: any) => new Date(h.time).getTime() > Date.now());
|
||||||
api.hourly(now.toISOString(), end),
|
loading.value = false;
|
||||||
])
|
});
|
||||||
if (hist.status === 'fulfilled') historic.value = hist.value.filter(r => r[props.metricKey] != null)
|
|
||||||
if (fore.status === 'fulfilled') forecast.value = fore.value.filter(r => r[props.metricKey] != null)
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
|
|
||||||
const nowLabel = computed(() => {
|
const nowLabel = computed(() => {
|
||||||
const d = new Date()
|
const d = new Date();
|
||||||
return `${d.getHours().toString().padStart(2,'0')}:${d.getMinutes().toString().padStart(2,'0')}`
|
return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
|
||||||
})
|
});
|
||||||
|
|
||||||
const chartData = computed(() => {
|
const chartData = computed(() => {
|
||||||
const color = meta.value?.color ?? '#38bdf8'
|
const color = meta.value?.color ?? '#38bdf8';
|
||||||
const histLabels = historic.value.map(r => r.time as string)
|
const histLabels = historic.value.map(r => r.time as string);
|
||||||
const foreLabels = forecast.value.map(r => r.time as string)
|
const foreLabels = forecast.value.map(r => r.time as string);
|
||||||
const histVals = historic.value.map(r => r[props.metricKey] as number)
|
const histVals = historic.value.map(r => r[props.metricKey] as number);
|
||||||
const foreVals = forecast.value.map(r => r[props.metricKey] as number)
|
const foreVals = forecast.value.map(r => r[props.metricKey] as number);
|
||||||
const currentVal = props.currentData[props.metricKey] as number | null
|
const currentVal = props.currentData[props.metricKey] as number | null;
|
||||||
|
|
||||||
const allLabels = [...histLabels, nowLabel.value, ...foreLabels]
|
const allLabels = [...histLabels, nowLabel.value, ...foreLabels];
|
||||||
const histData = [...histVals, currentVal, ...foreVals.map(() => null)]
|
const histData = [...histVals, currentVal, ...foreVals.map(() => null)];
|
||||||
const foreData = [...histVals.map(() => null), currentVal, ...foreVals]
|
const foreData = [...histVals.map(() => null), currentVal, ...foreVals];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
labels: allLabels,
|
labels: allLabels,
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Historic',
|
label: 'Historic',
|
||||||
data: histData,
|
data: histData,
|
||||||
borderColor: color,
|
borderColor: color,
|
||||||
backgroundColor: `${color}22`,
|
backgroundColor: `${color}22`,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
tension: 0.3,
|
tension: 0.3,
|
||||||
fill: true,
|
fill: true,
|
||||||
spanGaps: false,
|
spanGaps: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Forecast',
|
label: 'Forecast',
|
||||||
data: foreData,
|
data: foreData,
|
||||||
borderColor: color,
|
borderColor: color,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderDash: [6, 4],
|
borderDash: [6, 4],
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
tension: 0.3,
|
tension: 0.3,
|
||||||
fill: false,
|
fill: false,
|
||||||
spanGaps: false,
|
spanGaps: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Now',
|
label: 'Now',
|
||||||
data: allLabels.map(l => l === nowLabel.value ? currentVal : null),
|
data: allLabels.map(l => l === nowLabel.value ? currentVal : null),
|
||||||
borderColor: '#ffffff88',
|
borderColor: '#ffffff88',
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
pointRadius: 6,
|
pointRadius: 6,
|
||||||
pointHoverRadius:8,
|
pointHoverRadius: 8,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
showLine: false,
|
showLine: false,
|
||||||
spanGaps: false,
|
spanGaps: false,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
const chartOptions = computed<ChartOptions<'line'>>(() => ({
|
const chartOptions = computed<ChartOptions<'line'>>(() => ({
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
interaction: { mode: 'index', intersect: false },
|
interaction: {mode: 'index', intersect: false},
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: false },
|
legend: {display: false},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
callbacks: {
|
callbacks: {
|
||||||
label: ctx => formatValue(props.metricKey, ctx.parsed.y),
|
label: ctx => formatValue(props.metricKey, ctx.parsed.y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
ticks: {
|
ticks: {
|
||||||
color: 'var(--text-muted)',
|
color: 'var(--text-muted)',
|
||||||
maxTicksLimit: 12,
|
maxTicksLimit: 12,
|
||||||
maxRotation: 0,
|
maxRotation: 0,
|
||||||
callback(val, i) {
|
callback(val, i) {
|
||||||
const lbl = this.getLabelForValue(i)
|
const lbl = this.getLabelForValue(i);
|
||||||
return lbl?.length === 5 && lbl.endsWith(':00') ? lbl : ''
|
return lbl?.length === 5 && lbl.endsWith(':00') ? lbl : '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grid: { color: 'var(--border)' },
|
grid: {color: 'var(--border)'},
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
ticks: { color: 'var(--text-muted)', callback: v => formatValue(props.metricKey, v as number) },
|
ticks: {color: 'var(--text-muted)', callback: v => formatValue(props.metricKey, v as number)},
|
||||||
grid: { color: 'var(--border)' },
|
grid: {color: 'var(--border)'},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
annotation: {
|
annotation: {
|
||||||
annotations: {
|
annotations: {
|
||||||
nowLine: {
|
nowLine: {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
xMin: nowLabel.value,
|
xMin: nowLabel.value,
|
||||||
xMax: nowLabel.value,
|
xMax: nowLabel.value,
|
||||||
borderColor: '#ffffff44',
|
borderColor: '#ffffff44',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderDash: [4, 4],
|
borderDash: [4, 4],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.chart-wrap {
|
.chart-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 260px;
|
height: 260px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 260px;
|
height: 260px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="loading" class="loading">Loading chart…</div>
|
<div v-if="loading" class="loading">Loading chart…</div>
|
||||||
<div v-else class="chart-wrap">
|
<div v-else class="chart-wrap">
|
||||||
<Line :data="chartData" :options="chartOptions" />
|
<Line :data="chartData" :options="chartOptions"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -45,12 +45,12 @@ const arc = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const splitIdx = Math.round(pct * 200);
|
const splitIdx = Math.round(pct * 200);
|
||||||
const { x: cx, y: cy } = pts[splitIdx];
|
const { x: cx, y: cy } = <any>pts[splitIdx];
|
||||||
|
|
||||||
// Filled traveled path: close down to horizon baseline for fill
|
// Filled traveled path: close down to horizon baseline for fill
|
||||||
const traveledPts = pts.slice(0, splitIdx + 1);
|
const traveledPts = pts.slice(0, splitIdx + 1);
|
||||||
const filledPath = traveledPts.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ')
|
const filledPath = traveledPts.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ')
|
||||||
+ ` L${traveledPts[traveledPts.length - 1].x.toFixed(1)},${HORIZON}`
|
+ ` L${(<any>traveledPts[traveledPts.length - 1]).x.toFixed(1)},${HORIZON}`
|
||||||
+ ` L${x1},${HORIZON} Z`;
|
+ ` L${x1},${HORIZON} Z`;
|
||||||
|
|
||||||
// Remaining path stroke only
|
// Remaining path stroke only
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {getADSB, getAirTrafficHistory, getShapes, initAircraftDb} from './adsb.m
|
|||||||
import {getWeatherCondition} from './openweather.mjs';
|
import {getWeatherCondition} from './openweather.mjs';
|
||||||
import {lastForecast, getForecast, forecastTTL} from './forecast.mjs';
|
import {lastForecast, getForecast, forecastTTL} from './forecast.mjs';
|
||||||
import {dailyWeather, hourlyWeather} from './openmeteo.mjs';
|
import {dailyWeather, hourlyWeather} from './openmeteo.mjs';
|
||||||
import {init} from 'express/lib/application.js';
|
|
||||||
// import {Aurora} from './aurora.mjs';
|
// import {Aurora} from './aurora.mjs';
|
||||||
|
|
||||||
// ── Uncaught error handlers ───────────────────────────────────────────────────
|
// ── Uncaught error handlers ───────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user