add graph legends
This commit is contained in:
@@ -31,6 +31,12 @@ $major-tick: $gray-4;
|
|||||||
$minor-tick: $gray-5;
|
$minor-tick: $gray-5;
|
||||||
|
|
||||||
$text-font: Helvetica, sans-serif;
|
$text-font: Helvetica, sans-serif;
|
||||||
|
$normal-text-size: 16px;
|
||||||
|
$small-text-size: 12px;
|
||||||
|
|
||||||
|
$monospace-text-font: Monaco, monospace, monospace;
|
||||||
|
$normal-monospace-text-size: 12px;
|
||||||
|
$small-monospace-text-size: 10px;
|
||||||
|
|
||||||
$colors: (
|
$colors: (
|
||||||
$red-1,
|
$red-1,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, provide, ref } from 'vue';
|
import { computed, provide, ref } from 'vue';
|
||||||
import { useNow } from '@/composables/ticker';
|
import { useNow } from '@/composables/ticker';
|
||||||
import { GRAPH_DATA, type GraphData } from '@/graph/graph';
|
import { GRAPH_DATA, type GraphData, GraphSide } from '@/graph/graph';
|
||||||
import TimeText from '@/components/TimeText.vue';
|
import TimeText from '@/components/TimeText.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -13,6 +13,7 @@ const props = defineProps<{
|
|||||||
right_axis?: boolean;
|
right_axis?: boolean;
|
||||||
hide_time_labels?: boolean;
|
hide_time_labels?: boolean;
|
||||||
hide_time_ticks?: boolean;
|
hide_time_ticks?: boolean;
|
||||||
|
legend?: GraphSide;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const width = computed(() => {
|
const width = computed(() => {
|
||||||
@@ -62,8 +63,9 @@ const time_lines = [
|
|||||||
time_lines.reverse();
|
time_lines.reverse();
|
||||||
const text_offset = computed(() => 5);
|
const text_offset = computed(() => 5);
|
||||||
|
|
||||||
const border_left = computed(() => props.left_axis ? 96 : 0);
|
const legend_width = 160;
|
||||||
const border_right = computed(() => props.right_axis ? 80 : 0);
|
const border_left = computed(() => (props.left_axis ? 96 : 0) + (props.legend == GraphSide.Left ? legend_width : 0));
|
||||||
|
const border_right = computed(() => (props.right_axis ? 80 : 0) + (props.legend == GraphSide.Right ? legend_width : 0));
|
||||||
const border_top = computed(() => 6);
|
const border_top = computed(() => 6);
|
||||||
const border_bottom = computed(() => props.hide_time_labels ? 6 : 24);
|
const border_bottom = computed(() => props.hide_time_labels ? 6 : 24);
|
||||||
|
|
||||||
@@ -81,6 +83,13 @@ const x_map = (x: number) => {
|
|||||||
|
|
||||||
const telemetry_lines = ref([]);
|
const telemetry_lines = ref([]);
|
||||||
|
|
||||||
|
const legend_enabled = computed(() => props.legend === GraphSide.Left || props.legend === GraphSide.Right);
|
||||||
|
const legend_x = computed(() => (props.legend === GraphSide.Left) ? (8) : (width.value - legend_width + 8));
|
||||||
|
const legend_y = computed(() => border_top.value);
|
||||||
|
const legend_x_stride = computed(() => 0);
|
||||||
|
const legend_y_stride = computed(() => 16);
|
||||||
|
const legend_width_output = computed(() => legend_width - 8);
|
||||||
|
|
||||||
provide<GraphData>(GRAPH_DATA, {
|
provide<GraphData>(GRAPH_DATA, {
|
||||||
border_top: border_top,
|
border_top: border_top,
|
||||||
min_x: min_x,
|
min_x: min_x,
|
||||||
@@ -90,6 +99,12 @@ provide<GraphData>(GRAPH_DATA, {
|
|||||||
x_map: x_map,
|
x_map: x_map,
|
||||||
lines: telemetry_lines,
|
lines: telemetry_lines,
|
||||||
max_update_rate: 1000 / 10,
|
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 line_duration = computed(() => {
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const smoothing_distance_x = 5;
|
const smoothing_distance_x = 5;
|
||||||
const maximum_minimum_separation_live = 100; // ms
|
const maximum_minimum_separation_live = 100; // ms
|
||||||
|
const legend_line_length = 8;
|
||||||
|
const legend_text_offset = 4;
|
||||||
|
|
||||||
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 min_sep = computed(() => Math.min(props.minimum_separation || 0, maximum_minimum_separation_live));
|
||||||
@@ -247,6 +249,30 @@ const current_value = computed(() => {
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const legend_x = computed(() => {
|
||||||
|
return toValue(graph_data.legend_x) + toValue(graph_data.legend_x_stride) * index.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const legend_y = computed(() => {
|
||||||
|
return toValue(graph_data.legend_y) + toValue(graph_data.legend_y_stride) * index.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const legend_text = computed(() => {
|
||||||
|
const max_chars = (toValue(graph_data.legend_width) - legend_line_length - legend_text_offset * 2) / 7;
|
||||||
|
const start_text = props.data;
|
||||||
|
if (start_text.length > max_chars) {
|
||||||
|
return start_text.substring(0, 3) + "..." + start_text.substring(start_text.length - max_chars + 6);
|
||||||
|
}
|
||||||
|
return start_text;
|
||||||
|
});
|
||||||
|
|
||||||
|
const legend_line = computed(() => {
|
||||||
|
let x = legend_x.value;
|
||||||
|
let y = legend_y.value;
|
||||||
|
return `${x},${y} ${x + legend_line_length},${y}`;
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -265,6 +291,17 @@ const current_value = computed(() => {
|
|||||||
:value="current_value"
|
:value="current_value"
|
||||||
>
|
>
|
||||||
</ValueLabel>
|
</ValueLabel>
|
||||||
|
<template v-if="toValue(graph_data.legend_enabled)">
|
||||||
|
<polyline
|
||||||
|
fill="none"
|
||||||
|
:points="legend_line"
|
||||||
|
></polyline>
|
||||||
|
<text
|
||||||
|
:x="legend_x + legend_line_length + legend_text_offset"
|
||||||
|
:y="legend_y">
|
||||||
|
{{ legend_text }}
|
||||||
|
</text>
|
||||||
|
</template>
|
||||||
</g>
|
</g>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -275,4 +312,14 @@ polyline {
|
|||||||
stroke-width: 1px;
|
stroke-width: 1px;
|
||||||
stroke: var(--indexed-color);
|
stroke: var(--indexed-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-family: variables.$monospace-text-font;
|
||||||
|
text-anchor: start;
|
||||||
|
stroke: variables.$text-color;
|
||||||
|
fill: variables.$text-color;
|
||||||
|
dominant-baseline: middle;
|
||||||
|
font-size: variables.$small-monospace-text-size;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { MaybeRefOrGetter, Ref } from 'vue';
|
import type { MaybeRefOrGetter, Ref } from 'vue';
|
||||||
|
|
||||||
export enum GraphSide {
|
export enum GraphSide {
|
||||||
|
Hidden,
|
||||||
Right,
|
Right,
|
||||||
Left,
|
Left,
|
||||||
Hidden,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GRAPH_DATA = Symbol();
|
export const GRAPH_DATA = Symbol();
|
||||||
@@ -17,4 +17,10 @@ export interface GraphData {
|
|||||||
x_map: (x: number) => number;
|
x_map: (x: number) => number;
|
||||||
lines: Ref<symbol[]>;
|
lines: Ref<symbol[]>;
|
||||||
max_update_rate: MaybeRefOrGetter<number>;
|
max_update_rate: MaybeRefOrGetter<number>;
|
||||||
|
legend_enabled: MaybeRefOrGetter<boolean>;
|
||||||
|
legend_x: MaybeRefOrGetter<number>;
|
||||||
|
legend_x_stride: MaybeRefOrGetter<number>;
|
||||||
|
legend_y: MaybeRefOrGetter<number>;
|
||||||
|
legend_y_stride: MaybeRefOrGetter<number>;
|
||||||
|
legend_width: MaybeRefOrGetter<number>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { provide } from 'vue';
|
|||||||
import Graph from '@/components/SvgGraph.vue';
|
import Graph from '@/components/SvgGraph.vue';
|
||||||
import Axis from '@/components/GraphAxis.vue';
|
import Axis from '@/components/GraphAxis.vue';
|
||||||
import Line from '@/components/TelemetryLine.vue';
|
import Line from '@/components/TelemetryLine.vue';
|
||||||
|
import { GraphSide } from '@/graph/graph';
|
||||||
|
|
||||||
const websocket = useWebsocket();
|
const websocket = useWebsocket();
|
||||||
provide(WEBSOCKET_SYMBOL, websocket);
|
provide(WEBSOCKET_SYMBOL, websocket);
|
||||||
@@ -15,6 +16,7 @@ provide(WEBSOCKET_SYMBOL, websocket);
|
|||||||
:width="800"
|
:width="800"
|
||||||
:height="400"
|
:height="400"
|
||||||
:right_axis="true"
|
:right_axis="true"
|
||||||
|
:legend="GraphSide.Left"
|
||||||
>
|
>
|
||||||
<Axis>
|
<Axis>
|
||||||
<Line data="simple_producer/time_offset"></Line>
|
<Line data="simple_producer/time_offset"></Line>
|
||||||
@@ -27,6 +29,7 @@ provide(WEBSOCKET_SYMBOL, websocket);
|
|||||||
:height="400"
|
:height="400"
|
||||||
:duration="60 * 1000 * 10"
|
:duration="60 * 1000 * 10"
|
||||||
:right_axis="true"
|
:right_axis="true"
|
||||||
|
:legend="GraphSide.Right"
|
||||||
>
|
>
|
||||||
<Axis>
|
<Axis>
|
||||||
<Line
|
<Line
|
||||||
|
|||||||
Reference in New Issue
Block a user