allows controlling duration on the frontend
This commit is contained in:
@@ -11,10 +11,14 @@ import {
|
||||
import { useNow } from '@/composables/ticker';
|
||||
import { GRAPH_DATA, type GraphData, GraphSide } from '@/graph/graph';
|
||||
import TimeText from '@/components/TimeText.vue';
|
||||
import { getDateString } from '@/datetime';
|
||||
import {
|
||||
getDateString,
|
||||
getDurationString,
|
||||
parseDurationString,
|
||||
} from '@/datetime';
|
||||
|
||||
const props = defineProps<{
|
||||
duration?: number;
|
||||
initial_duration?: number;
|
||||
utc?: boolean;
|
||||
left_axis?: boolean;
|
||||
right_axis?: boolean;
|
||||
@@ -56,12 +60,7 @@ onUnmounted(() => {
|
||||
});
|
||||
|
||||
const now = useNow(33);
|
||||
const window_duration = computed(() => {
|
||||
if (props.duration) {
|
||||
return props.duration;
|
||||
}
|
||||
return 10 * 1000; // 10 seconds
|
||||
});
|
||||
const window_duration = ref(props.initial_duration || 10 * 1000);
|
||||
|
||||
const time_lines = [
|
||||
1, // 1ms
|
||||
@@ -148,6 +147,18 @@ const max_x_text = computed({
|
||||
}
|
||||
},
|
||||
});
|
||||
const duration_text = computed({
|
||||
get() {
|
||||
return getDurationString(window_duration.value);
|
||||
},
|
||||
set(newValue) {
|
||||
const new_duration = parseDurationString(newValue);
|
||||
if (!Number.isNaN(new_duration)) {
|
||||
window_duration.value = Math.max(new_duration, 1);
|
||||
fetch_history.value++;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const x_map = (x: number) => {
|
||||
return (
|
||||
@@ -200,13 +211,9 @@ const mouse_x = ref<number | null>(null);
|
||||
|
||||
function onMouseMove(event: MouseEvent) {
|
||||
if (props.cursor) {
|
||||
const new_x =
|
||||
mouse_x.value =
|
||||
event.clientX -
|
||||
(event.currentTarget as Element).getBoundingClientRect().left;
|
||||
if (new_x == 0) {
|
||||
console.log(event);
|
||||
}
|
||||
mouse_x.value = new_x;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,8 +227,6 @@ const mouse_t = computed(() => {
|
||||
}
|
||||
const t = inv_x_map(mouse_x.value);
|
||||
if (t < min_x.value || t > max_x.value) {
|
||||
// console.log(`x ${mouse_x.value} t ${t} min ${t < min_x.value} max ${t > max_x.value}`)
|
||||
// debugger;
|
||||
return null;
|
||||
}
|
||||
return t;
|
||||
@@ -237,10 +242,16 @@ const show_data_at_time = computed(() => {
|
||||
|
||||
const should_fade = ref(false);
|
||||
|
||||
const max_temporal_resolution = computed(() => {
|
||||
const delta_t = window_duration.value;
|
||||
return Math.floor(delta_t / 1000); // Aim for a maximum of 1000 data points
|
||||
});
|
||||
|
||||
provide<GraphData>(GRAPH_DATA, {
|
||||
border_top: border_top,
|
||||
min_x: min_x,
|
||||
max_x: max_x,
|
||||
max_temporal_resolution: max_temporal_resolution,
|
||||
live: live,
|
||||
fetch_history: fetch_history,
|
||||
width: () =>
|
||||
@@ -269,6 +280,14 @@ provide<GraphData>(GRAPH_DATA, {
|
||||
:style="`height: ${controls_height}px`"
|
||||
>
|
||||
<div class="grow"></div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
:size="15"
|
||||
v-model="duration_text"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
@@ -401,7 +420,7 @@ div.controls-header {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-content: stretch;
|
||||
gap: 1em 0;
|
||||
gap: 0 1em;
|
||||
margin: 0 1em 0 1em;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,18 +37,26 @@ const legend_text_offset = 4;
|
||||
const marker_radius = 3;
|
||||
|
||||
const text_offset = computed(() => 10);
|
||||
const min_sep = computed(() =>
|
||||
Math.min(props.minimum_separation || 0, maximum_minimum_separation_live),
|
||||
);
|
||||
|
||||
const graph_data = inject<GraphData>(GRAPH_DATA)!;
|
||||
const axis_data = inject<AxisData>(AXIS_DATA)!;
|
||||
|
||||
const data_min_sep = computed(() => {
|
||||
return Math.max(
|
||||
props.minimum_separation || 0,
|
||||
toValue(graph_data.max_temporal_resolution),
|
||||
);
|
||||
});
|
||||
|
||||
const live_min_sep = computed(() =>
|
||||
Math.min(data_min_sep.value, maximum_minimum_separation_live),
|
||||
);
|
||||
|
||||
const { data: telemetry_data } = useTelemetry(() => props.data);
|
||||
const websocket = inject<ShallowRef<WebsocketHandle>>(WEBSOCKET_SYMBOL)!;
|
||||
const value = websocket.value.listen_to_telemetry(
|
||||
telemetry_data,
|
||||
min_sep,
|
||||
live_min_sep,
|
||||
graph_data.live,
|
||||
);
|
||||
|
||||
@@ -95,7 +103,7 @@ watch([value], ([val]) => {
|
||||
x: val_t,
|
||||
y: item_val,
|
||||
} as Point;
|
||||
memo.value.insert(new_item, props.minimum_separation);
|
||||
memo.value.insert(new_item, data_min_sep.value);
|
||||
if (item_val < min.value) {
|
||||
min.value = item_val;
|
||||
}
|
||||
@@ -118,7 +126,7 @@ watch(
|
||||
const min_x = new Date(toValue(graph_data.min_x));
|
||||
const max_x = new Date(toValue(graph_data.max_x));
|
||||
const res = await fetch(
|
||||
`/api/tlm/history/${uuid}?from=${min_x.toISOString()}&to=${max_x.toISOString()}&resolution=${props.minimum_separation || 0}`,
|
||||
`/api/tlm/history/${uuid}?from=${min_x.toISOString()}&to=${max_x.toISOString()}&resolution=${data_min_sep.value}`,
|
||||
);
|
||||
const response = (await res.json()) as TelemetryDataItem[];
|
||||
for (const data_item of response) {
|
||||
@@ -136,9 +144,7 @@ watch(
|
||||
max.value = item_val;
|
||||
}
|
||||
}
|
||||
memo.value.reduce_to_maximum_separation(
|
||||
props.minimum_separation || 0,
|
||||
);
|
||||
memo.value.reduce_to_maximum_separation(data_min_sep.value);
|
||||
triggerRef(memo);
|
||||
debounced_recompute();
|
||||
recompute_bounds.value++;
|
||||
@@ -438,6 +444,11 @@ function onMouseExit(event: MouseEvent) {
|
||||
<div class="row">
|
||||
<span>{{ telemetry_data?.data_type }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span>{{
|
||||
current_data_point?.y || 'Missing Data'
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="row">
|
||||
|
||||
@@ -20,7 +20,7 @@ const height = ref(0);
|
||||
|
||||
function update_for_element(element: Element) {
|
||||
top.value = element.getBoundingClientRect().top + window.scrollY;
|
||||
left.value = element.getBoundingClientRect().left + window.screenX;
|
||||
left.value = element.getBoundingClientRect().left + window.scrollX;
|
||||
width.value = element.getBoundingClientRect().width;
|
||||
height.value = element.getBoundingClientRect().height;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// This function is slow
|
||||
export function getDateString(date: Date, utc: boolean, millis: boolean) {
|
||||
export function getDateString(
|
||||
date: Date,
|
||||
utc: boolean,
|
||||
millis: boolean,
|
||||
): string {
|
||||
const year = utc ? date.getUTCFullYear() : date.getFullYear();
|
||||
const month = (
|
||||
(utc ? date.getMonth() : date.getMonth()) + 1
|
||||
@@ -47,3 +51,47 @@ export function getDateString(date: Date, utc: boolean, millis: boolean) {
|
||||
});
|
||||
return `${year}/${month}/${day} ${hour}:${minute}:${second}${millis ? `.${milliseconds}` : ''}${utc ? 'Z' : ''}`;
|
||||
}
|
||||
|
||||
export function getDurationString(duration_millis: number): string {
|
||||
const millis = duration_millis % 1000;
|
||||
const duration_secs = Math.floor(duration_millis / 1000);
|
||||
const seconds = duration_secs % 60;
|
||||
const duration_minutes = Math.floor(duration_secs / 60);
|
||||
const minutes = duration_minutes % 60;
|
||||
const duration_hours = Math.floor(duration_minutes / 60);
|
||||
|
||||
const millis_str = millis.toLocaleString('en-US', {
|
||||
minimumIntegerDigits: 3,
|
||||
useGrouping: false,
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
const seconds_str = seconds.toLocaleString('en-US', {
|
||||
minimumIntegerDigits: 2,
|
||||
useGrouping: false,
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
const minutes_str = minutes.toLocaleString('en-US', {
|
||||
minimumIntegerDigits: 2,
|
||||
useGrouping: false,
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
const hours_str = duration_hours.toLocaleString('en-US', {
|
||||
minimumIntegerDigits: 1,
|
||||
useGrouping: false,
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
return `${hours_str}:${minutes_str}:${seconds_str}.${millis_str}`;
|
||||
}
|
||||
|
||||
export function parseDurationString(duration: string): number {
|
||||
const parts = duration.split(':');
|
||||
const seconds_str = parts.length >= 1 ? parts[parts.length - 1] : '0';
|
||||
const minutes_str = parts.length >= 2 ? parts[parts.length - 2] : '0';
|
||||
const hours_str = parts.length >= 3 ? parts[parts.length - 3] : '0';
|
||||
|
||||
const seconds = Number.parseFloat(seconds_str);
|
||||
const minutes = Number.parseFloat(minutes_str);
|
||||
const hours = Number.parseFloat(hours_str);
|
||||
|
||||
return Math.floor(((hours * 60 + minutes) * 60 + seconds) * 1000);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface GraphData {
|
||||
border_top: MaybeRefOrGetter<number>;
|
||||
min_x: MaybeRefOrGetter<number>;
|
||||
max_x: MaybeRefOrGetter<number>;
|
||||
max_temporal_resolution: MaybeRefOrGetter<number>;
|
||||
live: MaybeRefOrGetter<boolean>;
|
||||
fetch_history: MaybeRefOrGetter<number>;
|
||||
width: MaybeRefOrGetter<number>;
|
||||
|
||||
@@ -22,43 +22,19 @@ provide(WEBSOCKET_SYMBOL, websocket);
|
||||
</div>
|
||||
<div style="width: 100vw; height: 50vh">
|
||||
<SvgGraph
|
||||
:duration="60 * 1000 * 10"
|
||||
:initial_duration="60 * 1000 * 10"
|
||||
:legend="GraphSide.Right"
|
||||
right_axis
|
||||
>
|
||||
<Axis>
|
||||
<Line
|
||||
data="simple_producer/sin"
|
||||
:minimum_separation="1000"
|
||||
></Line>
|
||||
<Line
|
||||
data="simple_producer/cos4"
|
||||
:minimum_separation="1000"
|
||||
></Line>
|
||||
<Line
|
||||
data="simple_producer/sin2"
|
||||
:minimum_separation="1000"
|
||||
></Line>
|
||||
<Line
|
||||
data="simple_producer/cos"
|
||||
:minimum_separation="1000"
|
||||
></Line>
|
||||
<Line
|
||||
data="simple_producer/sin3"
|
||||
:minimum_separation="1000"
|
||||
></Line>
|
||||
<Line
|
||||
data="simple_producer/cos2"
|
||||
:minimum_separation="1000"
|
||||
></Line>
|
||||
<Line
|
||||
data="simple_producer/sin4"
|
||||
:minimum_separation="1000"
|
||||
></Line>
|
||||
<Line
|
||||
data="simple_producer/cos3"
|
||||
:minimum_separation="1000"
|
||||
></Line>
|
||||
<Line data="simple_producer/sin"></Line>
|
||||
<Line data="simple_producer/cos4"></Line>
|
||||
<Line data="simple_producer/sin2"></Line>
|
||||
<Line data="simple_producer/cos"></Line>
|
||||
<Line data="simple_producer/sin3"></Line>
|
||||
<Line data="simple_producer/cos2"></Line>
|
||||
<Line data="simple_producer/sin4"></Line>
|
||||
<Line data="simple_producer/cos3"></Line>
|
||||
</Axis>
|
||||
</SvgGraph>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user