allows scrolling backwards through history

This commit is contained in:
2025-01-05 10:52:09 -05:00
parent 32fcbbd916
commit 2cb1eec404
11 changed files with 214 additions and 87 deletions

View File

@@ -3,6 +3,7 @@ import { computed, inject, provide, ref, toValue, watch } from 'vue';
import { AXIS_DATA, type AxisData, AxisType } from '@/graph/axis';
import { GRAPH_DATA, type GraphData, GraphSide } from '@/graph/graph';
import NumericText from '@/components/NumericText.vue';
import { useNow } from '@/composables/ticker';
const props = defineProps<{
y_limits?: [number, number];
@@ -28,7 +29,9 @@ const raw_max_y = ref(-Infinity);
const axis_update_watch = ref(0);
watch([graph_data.min_x, graph_data.max_x], () => {
const axis_update_ticker = useNow(50);
watch([graph_data.min_x, graph_data.max_x, axis_update_ticker], () => {
axis_update_watch.value++;
min_y.value = raw_min_y.value;
max_y.value = raw_max_y.value;

View File

@@ -11,6 +11,7 @@ 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';
const props = defineProps<{
duration?: number;
@@ -107,11 +108,47 @@ const border_right = computed(
const border_top = computed(() => 6);
const border_bottom = computed(() => (props.hide_time_labels ? 6 : 24));
const max_x = now;
const max_x = ref(now.value);
const min_x = computed(() => max_x.value - window_duration.value);
const fetch_history = ref(0);
const diff_x = computed(() => max_x.value - min_x.value);
const live = ref(true);
watch([live], ([live_value]) => {
if (live_value) {
fetch_history.value++;
}
});
const valid_max_x_text = ref(true);
watch([now], ([now_value]) => {
if (live.value) {
max_x.value = now_value;
valid_max_x_text.value = true;
}
});
const max_x_text = computed({
// getter
get() {
return getDateString(new Date(max_x.value), props.utc, true);
},
// setter
set(newValue) {
const new_max_x = Date.parse(
newValue.replace('/', '-').replace('/', '-').replace(' ', 'T'),
);
if (!Number.isNaN(new_max_x)) {
fetch_history.value++;
max_x.value = new_max_x;
valid_max_x_text.value = true;
} else {
valid_max_x_text.value = false;
}
},
});
const x_map = (x: number) => {
return (
((width.value - border_left.value - border_right.value) *
@@ -203,7 +240,9 @@ const should_fade = ref(false);
provide<GraphData>(GRAPH_DATA, {
border_top: border_top,
min_x: min_x,
max_x: now,
max_x: max_x,
live: live,
fetch_history: fetch_history,
width: () =>
Math.max(width.value - border_left.value - border_right.value, 0),
height: () =>
@@ -231,7 +270,17 @@ provide<GraphData>(GRAPH_DATA, {
>
<div class="grow"></div>
<div>
<span>Duration Dropdown</span>
<input
type="text"
autocomplete="off"
:disabled="live"
:size="max_x_text.length"
v-model="max_x_text"
/>
</div>
<div>
<input type="checkbox" v-model="live" />
<label>Live</label>
</div>
</div>
<svg

View File

@@ -28,7 +28,6 @@ const props = defineProps<{
data: string;
minimum_separation?: number;
class?: string;
fetch_history?: number;
}>();
const smoothing_distance_x = 5;
@@ -42,13 +41,17 @@ const min_sep = computed(() =>
Math.min(props.minimum_separation || 0, 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);
const graph_data = inject<GraphData>(GRAPH_DATA)!;
const axis_data = inject<AxisData>(AXIS_DATA)!;
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,
graph_data.live,
);
const min = ref(Infinity);
const max = ref(-Infinity);
@@ -104,8 +107,9 @@ watch([value], ([val]) => {
}
}
});
const recompute_bounds = ref(0);
watch(
[telemetry_data, () => props.fetch_history],
[telemetry_data, () => toValue(graph_data.fetch_history)],
async ([data]) => {
if (data) {
const uuid = data.uuid;
@@ -137,9 +141,10 @@ watch(
);
triggerRef(memo);
debounced_recompute();
recompute_bounds.value++;
} catch (e) {
// TODO: Response?
console.log(e);
console.error(e);
}
}
},
@@ -170,19 +175,22 @@ watch([graph_data.min_x, graph_data.max_x], ([min_x, max_x]) => {
}
}
if (memo_changed) {
let min_val = Infinity;
let max_val = -Infinity;
for (let i = 1; i < memo.value.data.length; i++) {
const item_val = memo.value.data[i].y;
min_val = Math.min(min_val, item_val);
max_val = Math.max(max_val, item_val);
}
triggerRef(memo);
debounced_recompute();
max.value = max_val;
min.value = min_val;
recompute_bounds.value++;
}
});
watch([recompute_bounds], () => {
let min_val = Infinity;
let max_val = -Infinity;
for (let i = 1; i < memo.value.data.length; i++) {
const item_val = memo.value.data[i].y;
min_val = Math.min(min_val, item_val);
max_val = Math.max(max_val, item_val);
}
triggerRef(memo);
debounced_recompute();
max.value = max_val;
min.value = min_val;
});
watch(
[min, axis_data.axis_update_watch],
@@ -354,7 +362,7 @@ function onMouseExit(event: MouseEvent) {
:cx="marker_radius"
:cy="marker_radius"
:r="marker_radius"
:class="`indexed-color color-${index}`"
:class="`indexed-color color-${index} marker`"
/>
</marker>
</defs>
@@ -446,6 +454,10 @@ function onMouseExit(event: MouseEvent) {
<style lang="scss">
@use '@/assets/variables';
.fade rect.fade_other_selected {
opacity: 10%;
}
.fade .fade_other_selected {
opacity: 25%;
}
@@ -463,6 +475,11 @@ function onMouseExit(event: MouseEvent) {
fill: var(--indexed-color);
}
circle.marker {
stroke: variables.$background-color;
stroke-width: 1px;
}
polyline {
stroke-width: 1px;
}

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue';
import { getDateString } from '@/datetime';
const props = defineProps<{
x: number;
@@ -9,57 +10,12 @@ const props = defineProps<{
show_millis?: boolean;
}>();
// This function is slow
function getDateString(date: Date) {
const year = props.utc ? date.getUTCFullYear() : date.getFullYear();
const month = (
(props.utc ? date.getMonth() : date.getMonth()) + 1
).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false,
maximumFractionDigits: 0,
});
const day = (props.utc ? date.getUTCDate() : date.getDate()).toLocaleString(
'en-US',
{
minimumIntegerDigits: 2,
useGrouping: false,
maximumFractionDigits: 0,
},
);
const hour = (
props.utc ? date.getUTCHours() : date.getHours()
).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false,
maximumFractionDigits: 0,
});
const minute = (
props.utc ? date.getUTCMinutes() : date.getMinutes()
).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false,
maximumFractionDigits: 0,
});
const second = (
props.utc ? date.getUTCSeconds() : date.getSeconds()
).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false,
maximumFractionDigits: 0,
});
const milliseconds = (
props.utc ? date.getUTCMilliseconds() : date.getMilliseconds()
).toLocaleString('en-US', {
minimumIntegerDigits: 3,
useGrouping: false,
maximumFractionDigits: 0,
});
return `${year}/${month}/${day} ${hour}:${minute}:${second}${props.show_millis ? `.${milliseconds}` : ''}${props.utc ? 'Z' : ''}`;
}
const timetext = computed(() => {
return getDateString(new Date(props.timestamp));
return getDateString(
new Date(props.timestamp),
props.utc,
props.show_millis,
);
});
</script>

View File

@@ -3,6 +3,7 @@ import {
type MaybeRefOrGetter,
onMounted,
onUnmounted,
onWatcherCleanup,
ref,
type Ref,
shallowRef,
@@ -95,6 +96,7 @@ export class WebsocketHandle {
listen_to_telemetry(
telemetry: MaybeRefOrGetter<TelemetryDefinition | null>,
minimum_separation_ms: MaybeRefOrGetter<number> | undefined,
live: MaybeRefOrGetter<boolean>,
) {
const value_result = ref<TelemetryDataItem | null>(null);
@@ -114,10 +116,12 @@ export class WebsocketHandle {
return 0;
});
const is_live = computed(() => toValue(live));
watch(
[uuid, this.connected, minimum_separation],
([uuid_value, connected, min_sep]) => {
if (connected && uuid_value) {
[uuid, this.connected, minimum_separation, is_live],
([uuid_value, connected, min_sep, live_value]) => {
if (connected && uuid_value && live_value) {
this.websocket?.send(
JSON.stringify({
RegisterTlmListener: {
@@ -129,8 +133,26 @@ export class WebsocketHandle {
if (!this.on_telem_value.has(uuid_value)) {
this.on_telem_value.set(uuid_value, []);
}
this.on_telem_value.get(uuid_value)?.push((value) => {
const callback_fn = (value: TelemetryDataItem) => {
value_result.value = value;
};
this.on_telem_value.get(uuid_value)?.push(callback_fn);
onWatcherCleanup(() => {
this.websocket?.send(
JSON.stringify({
UnregisterTlmListener: {
uuid: uuid_value,
},
}),
);
const index = this.on_telem_value
.get(uuid_value)
?.indexOf(callback_fn);
if (index !== undefined && index >= 0) {
this.on_telem_value
.get(uuid_value)
?.splice(index, 1);
}
});
}
},

49
frontend/src/datetime.ts Normal file
View File

@@ -0,0 +1,49 @@
// This function is slow
export function getDateString(date: Date, utc: boolean, millis: boolean) {
const year = utc ? date.getUTCFullYear() : date.getFullYear();
const month = (
(utc ? date.getMonth() : date.getMonth()) + 1
).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false,
maximumFractionDigits: 0,
});
const day = (utc ? date.getUTCDate() : date.getDate()).toLocaleString(
'en-US',
{
minimumIntegerDigits: 2,
useGrouping: false,
maximumFractionDigits: 0,
},
);
const hour = (utc ? date.getUTCHours() : date.getHours()).toLocaleString(
'en-US',
{
minimumIntegerDigits: 2,
useGrouping: false,
maximumFractionDigits: 0,
},
);
const minute = (
utc ? date.getUTCMinutes() : date.getMinutes()
).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false,
maximumFractionDigits: 0,
});
const second = (
utc ? date.getUTCSeconds() : date.getSeconds()
).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false,
maximumFractionDigits: 0,
});
const milliseconds = (
utc ? date.getUTCMilliseconds() : date.getMilliseconds()
).toLocaleString('en-US', {
minimumIntegerDigits: 3,
useGrouping: false,
maximumFractionDigits: 0,
});
return `${year}/${month}/${day} ${hour}:${minute}:${second}${millis ? `.${milliseconds}` : ''}${utc ? 'Z' : ''}`;
}

View File

@@ -12,6 +12,8 @@ export interface GraphData {
border_top: MaybeRefOrGetter<number>;
min_x: MaybeRefOrGetter<number>;
max_x: MaybeRefOrGetter<number>;
live: MaybeRefOrGetter<boolean>;
fetch_history: MaybeRefOrGetter<number>;
width: MaybeRefOrGetter<number>;
height: MaybeRefOrGetter<number>;
x_map: (x: number) => number;