diff --git a/examples/simple_producer/src/main.rs b/examples/simple_producer/src/main.rs index d68821e..85760a5 100644 --- a/examples/simple_producer/src/main.rs +++ b/examples/simple_producer/src/main.rs @@ -190,8 +190,8 @@ async fn main() -> Result<(), Box> { let mut next_time = Instant::now(); let mut index = 0; while !cancellation_token.is_cancelled() { - next_time += Duration::from_millis(100); - index += 10; + next_time += Duration::from_millis(10); + index += 1; tokio::time::sleep_until(next_time).await; sin_tlm_handle .publish( diff --git a/frontend/src/components/SvgGraph.vue b/frontend/src/components/SvgGraph.vue index 0e07e7e..7155d3c 100644 --- a/frontend/src/components/SvgGraph.vue +++ b/frontend/src/components/SvgGraph.vue @@ -79,6 +79,7 @@ provide(GRAPH_DATA, { height: () => height.value - 2 * border_top_bottom.value, x_map: x_map, lines: telemetry_lines, + max_update_rate: 1000 / 10 }); const duration = computed(() => { diff --git a/frontend/src/components/TelemetryLine.vue b/frontend/src/components/TelemetryLine.vue index 81895e7..714ea79 100644 --- a/frontend/src/components/TelemetryLine.vue +++ b/frontend/src/components/TelemetryLine.vue @@ -19,6 +19,7 @@ import { import { GRAPH_DATA, type GraphData } from '@/graph/graph'; import { AXIS_DATA, type AxisData } from '@/graph/axis'; import ValueLabel from '@/components/ValueLabel.vue'; +import type { Point } from '@/graph/line'; const props = defineProps<{ data: string; @@ -39,6 +40,17 @@ const axis_data = inject(AXIS_DATA)!; const min = ref(Infinity); const max = ref(-Infinity); +const recompute_points = shallowRef(0); + +function trigger_recompute() { + recompute_points.value = Date.now(); +} +function debounced_recompute() { + if (recompute_points.value + toValue(graph_data.max_update_rate) < Date.now()) { + trigger_recompute(); + } +} + const line = ref(Symbol()); const index = computed(() => { return graph_data.lines.value.indexOf(line.value); @@ -52,15 +64,18 @@ onUnmounted(() => { ); }); -const memo = shallowRef([]); +const memo = shallowRef([]); watch([value], ([val]) => { const min_x = toValue(graph_data.min_x); if (val) { const val_t = Date.parse(val.timestamp); if (val_t >= min_x) { // TODO: Insert this in the right spot in memo - const new_memo = [val].concat(memo.value); const item_val = val.value[data.value!.data_type] as number; + const new_memo = [{ + x: val_t, + y: item_val, + }].concat(memo.value); if (item_val < min.value) { min.value = item_val; } @@ -68,16 +83,17 @@ watch([value], ([val]) => { max.value = item_val; } memo.value = new_memo; + debounced_recompute(); } } }); watch([graph_data.min_x, graph_data.max_x], ([min_x, max_x]) => { let memo_changed = false; - const new_memo = ([] as TelemetryDataItem[]).concat(memo.value); + const new_memo = ([] as Point[]).concat(memo.value); if (min_x) { while ( new_memo.length > 2 && - Date.parse(new_memo[new_memo.length - 2].timestamp) < toValue(min_x) + new_memo[new_memo.length - 2].x < toValue(min_x) ) { new_memo.pop(); memo_changed = true; @@ -86,7 +102,7 @@ watch([graph_data.min_x, graph_data.max_x], ([min_x, max_x]) => { if (max_x) { while ( new_memo.length > 2 && - Date.parse(new_memo[1].timestamp) > toValue(max_x) + new_memo[1].x > toValue(max_x) ) { new_memo.shift(); memo_changed = true; @@ -96,11 +112,12 @@ watch([graph_data.min_x, graph_data.max_x], ([min_x, max_x]) => { let min_val = Infinity; let max_val = -Infinity; for (const item of new_memo) { - const item_val = item.value[data.value!.data_type] as number; + const item_val = item.y; min_val = Math.min(min_val, item_val); max_val = Math.max(max_val, item_val); } memo.value = new_memo; + debounced_recompute(); max.value = max_val; min.value = min_val; } @@ -109,6 +126,7 @@ watch([graph_data.min_x, graph_data.max_x], ([min_x, max_x]) => { watch( [min, axis_data.axis_update_watch], ([min_val]) => { + trigger_recompute(); axis_data.min_y_callback(min_val); }, { @@ -119,6 +137,7 @@ watch( watch( [max, axis_data.axis_update_watch], ([max_val]) => { + trigger_recompute() axis_data.max_y_callback(max_val); }, { @@ -126,35 +145,50 @@ watch( }, ); -// This function is somewhat slow -const points = computed(() => { - let points = ''; - if (memo.value.length == 0 || data.value == null) { - return ''; - } +const points = ref(""); - let last_x = graph_data.x_map(toValue(graph_data.max_x)); - let last_t = toValue(graph_data.max_x) + smoothing_distance; +const old_max = ref(0); - for (const data_item of memo.value) { - const t = Date.parse(data_item.timestamp); - const v = data_item.value[data.value.data_type]; - const x = graph_data.x_map(t); - const y = axis_data.y_map(v); +const group_transform = computed(() => { + const new_max = toValue(graph_data.max_x); + const offset = graph_data.x_map(old_max.value) - graph_data.x_map(new_max); + return `translate(${offset} 0)`; +}) - if (last_t - t < smoothing_distance) { - points += ` ${x},${y}`; - } else { - points += ` ${last_x},${y} ${x},${y}`; +watch( + [recompute_points], + () => { + let new_points = ''; + if (memo.value.length == 0 || data.value == null) { + return ''; } - last_x = x; - last_t = t; - if (last_x <= 0.0) { - break; + + const future_number = toValue(graph_data.max_x) + 9999999999; + + let last_x = graph_data.x_map(future_number); + old_max.value = toValue(graph_data.max_x); + let last_t = future_number + smoothing_distance; + + for (const data_item of memo.value) { + const t = data_item.x; + const v = data_item.y; + const x = graph_data.x_map(t); + const y = axis_data.y_map(v); + + if (last_t - t < smoothing_distance) { + new_points += ` ${x},${y}`; + } else { + new_points += ` ${last_x},${y} ${x},${y}`; + } + last_x = x; + last_t = t; + if (last_x <= 0.0) { + break; + } } + points.value = new_points; } - return points; -}); +) const current_value = computed(() => { const val = value.value; @@ -167,11 +201,15 @@ const current_value = computed(() => {