allows controlling duration on the frontend
This commit is contained in:
@@ -11,10 +11,14 @@ import {
|
|||||||
import { useNow } from '@/composables/ticker';
|
import { useNow } from '@/composables/ticker';
|
||||||
import { GRAPH_DATA, type GraphData, GraphSide } from '@/graph/graph';
|
import { GRAPH_DATA, type GraphData, GraphSide } from '@/graph/graph';
|
||||||
import TimeText from '@/components/TimeText.vue';
|
import TimeText from '@/components/TimeText.vue';
|
||||||
import { getDateString } from '@/datetime';
|
import {
|
||||||
|
getDateString,
|
||||||
|
getDurationString,
|
||||||
|
parseDurationString,
|
||||||
|
} from '@/datetime';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
duration?: number;
|
initial_duration?: number;
|
||||||
utc?: boolean;
|
utc?: boolean;
|
||||||
left_axis?: boolean;
|
left_axis?: boolean;
|
||||||
right_axis?: boolean;
|
right_axis?: boolean;
|
||||||
@@ -56,12 +60,7 @@ onUnmounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const now = useNow(33);
|
const now = useNow(33);
|
||||||
const window_duration = computed(() => {
|
const window_duration = ref(props.initial_duration || 10 * 1000);
|
||||||
if (props.duration) {
|
|
||||||
return props.duration;
|
|
||||||
}
|
|
||||||
return 10 * 1000; // 10 seconds
|
|
||||||
});
|
|
||||||
|
|
||||||
const time_lines = [
|
const time_lines = [
|
||||||
1, // 1ms
|
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) => {
|
const x_map = (x: number) => {
|
||||||
return (
|
return (
|
||||||
@@ -200,13 +211,9 @@ const mouse_x = ref<number | null>(null);
|
|||||||
|
|
||||||
function onMouseMove(event: MouseEvent) {
|
function onMouseMove(event: MouseEvent) {
|
||||||
if (props.cursor) {
|
if (props.cursor) {
|
||||||
const new_x =
|
mouse_x.value =
|
||||||
event.clientX -
|
event.clientX -
|
||||||
(event.currentTarget as Element).getBoundingClientRect().left;
|
(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);
|
const t = inv_x_map(mouse_x.value);
|
||||||
if (t < min_x.value || t > max_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 null;
|
||||||
}
|
}
|
||||||
return t;
|
return t;
|
||||||
@@ -237,10 +242,16 @@ const show_data_at_time = computed(() => {
|
|||||||
|
|
||||||
const should_fade = ref(false);
|
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, {
|
provide<GraphData>(GRAPH_DATA, {
|
||||||
border_top: border_top,
|
border_top: border_top,
|
||||||
min_x: min_x,
|
min_x: min_x,
|
||||||
max_x: max_x,
|
max_x: max_x,
|
||||||
|
max_temporal_resolution: max_temporal_resolution,
|
||||||
live: live,
|
live: live,
|
||||||
fetch_history: fetch_history,
|
fetch_history: fetch_history,
|
||||||
width: () =>
|
width: () =>
|
||||||
@@ -269,6 +280,14 @@ provide<GraphData>(GRAPH_DATA, {
|
|||||||
:style="`height: ${controls_height}px`"
|
:style="`height: ${controls_height}px`"
|
||||||
>
|
>
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
autocomplete="off"
|
||||||
|
:size="15"
|
||||||
|
v-model="duration_text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -401,7 +420,7 @@ div.controls-header {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-content: stretch;
|
align-content: stretch;
|
||||||
gap: 1em 0;
|
gap: 0 1em;
|
||||||
margin: 0 1em 0 1em;
|
margin: 0 1em 0 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,18 +37,26 @@ const legend_text_offset = 4;
|
|||||||
const marker_radius = 3;
|
const marker_radius = 3;
|
||||||
|
|
||||||
const text_offset = computed(() => 10);
|
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 graph_data = inject<GraphData>(GRAPH_DATA)!;
|
||||||
const axis_data = inject<AxisData>(AXIS_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 { data: telemetry_data } = useTelemetry(() => props.data);
|
||||||
const websocket = inject<ShallowRef<WebsocketHandle>>(WEBSOCKET_SYMBOL)!;
|
const websocket = inject<ShallowRef<WebsocketHandle>>(WEBSOCKET_SYMBOL)!;
|
||||||
const value = websocket.value.listen_to_telemetry(
|
const value = websocket.value.listen_to_telemetry(
|
||||||
telemetry_data,
|
telemetry_data,
|
||||||
min_sep,
|
live_min_sep,
|
||||||
graph_data.live,
|
graph_data.live,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -95,7 +103,7 @@ watch([value], ([val]) => {
|
|||||||
x: val_t,
|
x: val_t,
|
||||||
y: item_val,
|
y: item_val,
|
||||||
} as Point;
|
} as Point;
|
||||||
memo.value.insert(new_item, props.minimum_separation);
|
memo.value.insert(new_item, data_min_sep.value);
|
||||||
if (item_val < min.value) {
|
if (item_val < min.value) {
|
||||||
min.value = item_val;
|
min.value = item_val;
|
||||||
}
|
}
|
||||||
@@ -118,7 +126,7 @@ watch(
|
|||||||
const min_x = new Date(toValue(graph_data.min_x));
|
const min_x = new Date(toValue(graph_data.min_x));
|
||||||
const max_x = new Date(toValue(graph_data.max_x));
|
const max_x = new Date(toValue(graph_data.max_x));
|
||||||
const res = await fetch(
|
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[];
|
const response = (await res.json()) as TelemetryDataItem[];
|
||||||
for (const data_item of response) {
|
for (const data_item of response) {
|
||||||
@@ -136,9 +144,7 @@ watch(
|
|||||||
max.value = item_val;
|
max.value = item_val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
memo.value.reduce_to_maximum_separation(
|
memo.value.reduce_to_maximum_separation(data_min_sep.value);
|
||||||
props.minimum_separation || 0,
|
|
||||||
);
|
|
||||||
triggerRef(memo);
|
triggerRef(memo);
|
||||||
debounced_recompute();
|
debounced_recompute();
|
||||||
recompute_bounds.value++;
|
recompute_bounds.value++;
|
||||||
@@ -438,6 +444,11 @@ function onMouseExit(event: MouseEvent) {
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<span>{{ telemetry_data?.data_type }}</span>
|
<span>{{ telemetry_data?.data_type }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span>{{
|
||||||
|
current_data_point?.y || 'Missing Data'
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const height = ref(0);
|
|||||||
|
|
||||||
function update_for_element(element: Element) {
|
function update_for_element(element: Element) {
|
||||||
top.value = element.getBoundingClientRect().top + window.scrollY;
|
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;
|
width.value = element.getBoundingClientRect().width;
|
||||||
height.value = element.getBoundingClientRect().height;
|
height.value = element.getBoundingClientRect().height;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
// This function is slow
|
// 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 year = utc ? date.getUTCFullYear() : date.getFullYear();
|
||||||
const month = (
|
const month = (
|
||||||
(utc ? date.getMonth() : date.getMonth()) + 1
|
(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' : ''}`;
|
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>;
|
border_top: MaybeRefOrGetter<number>;
|
||||||
min_x: MaybeRefOrGetter<number>;
|
min_x: MaybeRefOrGetter<number>;
|
||||||
max_x: MaybeRefOrGetter<number>;
|
max_x: MaybeRefOrGetter<number>;
|
||||||
|
max_temporal_resolution: MaybeRefOrGetter<number>;
|
||||||
live: MaybeRefOrGetter<boolean>;
|
live: MaybeRefOrGetter<boolean>;
|
||||||
fetch_history: MaybeRefOrGetter<number>;
|
fetch_history: MaybeRefOrGetter<number>;
|
||||||
width: MaybeRefOrGetter<number>;
|
width: MaybeRefOrGetter<number>;
|
||||||
|
|||||||
@@ -22,43 +22,19 @@ provide(WEBSOCKET_SYMBOL, websocket);
|
|||||||
</div>
|
</div>
|
||||||
<div style="width: 100vw; height: 50vh">
|
<div style="width: 100vw; height: 50vh">
|
||||||
<SvgGraph
|
<SvgGraph
|
||||||
:duration="60 * 1000 * 10"
|
:initial_duration="60 * 1000 * 10"
|
||||||
:legend="GraphSide.Right"
|
:legend="GraphSide.Right"
|
||||||
right_axis
|
right_axis
|
||||||
>
|
>
|
||||||
<Axis>
|
<Axis>
|
||||||
<Line
|
<Line data="simple_producer/sin"></Line>
|
||||||
data="simple_producer/sin"
|
<Line data="simple_producer/cos4"></Line>
|
||||||
:minimum_separation="1000"
|
<Line data="simple_producer/sin2"></Line>
|
||||||
></Line>
|
<Line data="simple_producer/cos"></Line>
|
||||||
<Line
|
<Line data="simple_producer/sin3"></Line>
|
||||||
data="simple_producer/cos4"
|
<Line data="simple_producer/cos2"></Line>
|
||||||
:minimum_separation="1000"
|
<Line data="simple_producer/sin4"></Line>
|
||||||
></Line>
|
<Line data="simple_producer/cos3"></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>
|
|
||||||
</Axis>
|
</Axis>
|
||||||
</SvgGraph>
|
</SvgGraph>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user