From c69022448f321d826ca78830e0f9b619e06b9ec4 Mon Sep 17 00:00:00 2001 From: Sergey Savelyev Date: Sat, 4 Jan 2025 11:45:09 -0500 Subject: [PATCH] add graph cursor --- frontend/src/assets/variables.scss | 2 + frontend/src/components/SvgGraph.vue | 131 ++++++++++++++++++---- frontend/src/components/TelemetryLine.vue | 42 ++++--- frontend/src/graph/graph.ts | 1 + frontend/src/views/HomeView.vue | 2 +- 5 files changed, 139 insertions(+), 39 deletions(-) diff --git a/frontend/src/assets/variables.scss b/frontend/src/assets/variables.scss index ea25cf6..0ccacde 100644 --- a/frontend/src/assets/variables.scss +++ b/frontend/src/assets/variables.scss @@ -1,5 +1,6 @@ @use 'sass:color'; +$gray-0: oklch(100% 0 0); $gray-1: oklch(90% 0 0); $gray-2: oklch(80% 0 0); $gray-3: oklch(70% 0 0); @@ -29,6 +30,7 @@ $background-color: $gray-7; $light-background-color: color.adjust($background-color, $lightness: 5%); $dark-background-color: color.adjust($background-color, $lightness: -5%); +$cursor-tick: $gray-0; $time-tick: $gray-1; $grid-line: $gray-1; $major-tick: $gray-4; diff --git a/frontend/src/components/SvgGraph.vue b/frontend/src/components/SvgGraph.vue index 96b4e9b..c5dac83 100644 --- a/frontend/src/components/SvgGraph.vue +++ b/frontend/src/components/SvgGraph.vue @@ -21,6 +21,7 @@ const props = defineProps<{ hide_time_ticks?: boolean; include_controls?: boolean; legend?: GraphSide; + cursor?: boolean; }>(); const divRef = useTemplateRef('graph-div'); @@ -109,15 +110,23 @@ const border_bottom = computed(() => (props.hide_time_labels ? 6 : 24)); const max_x = now; const min_x = computed(() => max_x.value - window_duration.value); +const diff_x = computed(() => max_x.value - min_x.value); + const x_map = (x: number) => { - const diff_x = max_x.value - min_x.value; return ( ((width.value - border_left.value - border_right.value) * (x - min_x.value)) / - diff_x + + diff_x.value + border_left.value ); }; +const inv_x_map = (x: number) => { + return ( + ((x - border_left.value) * diff_x.value) / + (width.value - border_left.value - border_right.value) + + min_x.value + ); +}; const telemetry_lines = ref([]); @@ -132,25 +141,6 @@ const legend_x_stride = computed(() => 0); const legend_y_stride = computed(() => 16); const legend_width_output = computed(() => legend_width - 8); -provide(GRAPH_DATA, { - border_top: border_top, - min_x: min_x, - max_x: now, - width: () => - Math.max(width.value - border_left.value - border_right.value, 0), - height: () => - Math.max(height.value - border_top.value - border_bottom.value, 0), - x_map: x_map, - lines: telemetry_lines, - max_update_rate: 1000 / 10, - legend_enabled: legend_enabled, - legend_x: legend_x, - legend_y: legend_y, - legend_x_stride: legend_x_stride, - legend_y_stride: legend_y_stride, - legend_width: legend_width_output, -}); - const line_duration = computed(() => { const diff_x = max_x.value - min_x.value; return time_lines.find((duration) => diff_x / duration >= 2)!; @@ -168,6 +158,65 @@ const lines = computed(() => { } return result; }); + +const mouse_x = ref(null); + +function onMouseMove(event: MouseEvent) { + if (props.cursor) { + const new_x = + event.clientX - + (event.currentTarget as Element).getBoundingClientRect().left; + if (new_x == 0) { + console.log(event); + } + mouse_x.value = new_x; + } +} + +function onMouseOut() { + mouse_x.value = null; +} + +const mouse_t = computed(() => { + if (mouse_x.value === null) { + return null; + } + 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; +}); + +const show_data_at_time = computed(() => { + if (mouse_t.value === null) { + return max_x.value; + } else { + return mouse_t.value; + } +}); + +provide(GRAPH_DATA, { + border_top: border_top, + min_x: min_x, + max_x: now, + width: () => + Math.max(width.value - border_left.value - border_right.value, 0), + height: () => + Math.max(height.value - border_top.value - border_bottom.value, 0), + x_map: x_map, + lines: telemetry_lines, + max_update_rate: 1000 / 10, + legend_enabled: legend_enabled, + legend_x: legend_x, + legend_y: legend_y, + legend_x_stride: legend_x_stride, + legend_y_stride: legend_y_stride, + legend_width: legend_width_output, + cursor_time: show_data_at_time, +}); + + + + + @@ -249,6 +326,16 @@ const lines = computed(() => { fill: variables.$time-tick; } +.cursor_tick { + stroke: variables.$cursor-tick; + fill: variables.$cursor-tick; +} + +.cursor_tick > rect { + fill: variables.$background-color; + opacity: 66%; +} + div.full-size { width: 100%; height: 100%; diff --git a/frontend/src/components/TelemetryLine.vue b/frontend/src/components/TelemetryLine.vue index 1849ae2..98af199 100644 --- a/frontend/src/components/TelemetryLine.vue +++ b/frontend/src/components/TelemetryLine.vue @@ -152,8 +152,8 @@ watch([graph_data.min_x, graph_data.max_x], ([min_x, max_x]) => { if (min_x) { while ( - memo.value.data.length > 2 && - memo.value.data[1].x < toValue(min_x) + memo.value.data.length > 1 && + memo.value.data[0].x < toValue(min_x) ) { memo.value.data.shift(); memo_changed = true; @@ -161,8 +161,8 @@ watch([graph_data.min_x, graph_data.max_x], ([min_x, max_x]) => { } if (max_x) { while ( - memo.value.data.length > 2 && - memo.value.data[memo.value.data.length - 2].x > toValue(max_x) + memo.value.data.length > 1 && + memo.value.data[memo.value.data.length - 1].x > toValue(max_x) ) { memo.value.data.pop(); memo_changed = true; @@ -221,10 +221,9 @@ watch([recompute_points], () => { return ''; } - const future_number = - toValue(graph_data.max_x) + toValue(graph_data.max_update_rate); - - let last_x = graph_data.x_map(future_number) + smoothing_distance_x; + let last_x = graph_data.x_map( + memo.value.data[memo.value.data.length - 1].x, + ); old_max.value = toValue(graph_data.max_x); for (let i = memo.value.data.length - 1; i >= 0; i--) { @@ -247,12 +246,22 @@ watch([recompute_points], () => { points.value = new_points; }); -const current_value = computed(() => { - const val = value.value; - if (val) { - return val.value[telemetry_data.value!.data_type] as number; +const current_data_point = computed(() => { + if (memo.value.data.length == 0) { + return undefined; } - return undefined; + const cursor_time = toValue(graph_data.cursor_time); + const index = Math.max(memo.value.find_index(cursor_time) - 1, 0); + return memo.value.data[index]; +}); + +const current_data_point_line = computed(() => { + if (!current_data_point.value) { + return ''; + } + const x = current_data_point.value.x; + const y = axis_data.y_map(current_data_point.value.y); + return `${graph_data.x_map(x)},${y} ${graph_data.x_map(toValue(graph_data.max_x))},${y}`; }); const legend_x = computed(() => { @@ -319,12 +328,13 @@ function onCloseLegend() { :transform="group_transform" :points="points" > +