improve numeric labeling
This commit is contained in:
@@ -1,18 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject, provide, ref, toValue, watch } from 'vue';
|
import { computed, inject, provide, ref, toValue, watch } from 'vue';
|
||||||
import { AXIS_DATA, type AxisData, AxisSide, AxisType } from '@/graph/axis';
|
import { AXIS_DATA, type AxisData, AxisType } from '@/graph/axis';
|
||||||
import { GRAPH_DATA, type GraphData } from '@/graph/graph';
|
import { GRAPH_DATA, type GraphData, GraphSide } from '@/graph/graph';
|
||||||
|
import NumericText from '@/components/NumericText.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
y_limits?: [number, number];
|
y_limits?: [number, number];
|
||||||
side?: AxisSide;
|
side?: GraphSide;
|
||||||
type?: AxisType;
|
type?: AxisType;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const minor_tick_length = computed(() => 4);
|
const minor_tick_length = computed(() => 4);
|
||||||
const major_tick_length = computed(() => 8);
|
const major_tick_length = computed(() => 8);
|
||||||
const text_offset = computed(() => 5);
|
const text_offset = computed(() => 5);
|
||||||
const side = computed(() => props.side || AxisSide.Right);
|
const side = computed(() => props.side || GraphSide.Right);
|
||||||
const type = computed(() => props.type || AxisType.Linear);
|
const type = computed(() => props.type || AxisType.Linear);
|
||||||
|
|
||||||
const ticker_locations = [Math.log10(1), Math.log10(2), Math.log10(5)];
|
const ticker_locations = [Math.log10(1), Math.log10(2), Math.log10(5)];
|
||||||
@@ -101,7 +102,7 @@ const max_y_value = computed(() => {
|
|||||||
|
|
||||||
const y_map = (y: number) => {
|
const y_map = (y: number) => {
|
||||||
const height = toValue(graph_data.height);
|
const height = toValue(graph_data.height);
|
||||||
const border_top_bottom = toValue(graph_data.border_top_bottom);
|
const border_top = toValue(graph_data.border_top);
|
||||||
let max_value = toValue(max_y_value);
|
let max_value = toValue(max_y_value);
|
||||||
let min_value = toValue(min_y_value);
|
let min_value = toValue(min_y_value);
|
||||||
let y_value = y;
|
let y_value = y;
|
||||||
@@ -111,7 +112,7 @@ const y_map = (y: number) => {
|
|||||||
y_value = Math.log(y_value);
|
y_value = Math.log(y_value);
|
||||||
}
|
}
|
||||||
const diff_y = max_value - min_value;
|
const diff_y = max_value - min_value;
|
||||||
return height * (1 - (y_value - min_value) / diff_y) + border_top_bottom;
|
return height * (1 - (y_value - min_value) / diff_y) + border_top;
|
||||||
};
|
};
|
||||||
|
|
||||||
provide<AxisData>(AXIS_DATA, {
|
provide<AxisData>(AXIS_DATA, {
|
||||||
@@ -167,7 +168,7 @@ const lines = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template v-if="side == AxisSide.Right">
|
<template v-if="side == GraphSide.Right">
|
||||||
<g class="minor_tick" clip-path="url(#y_ticker)">
|
<g class="minor_tick" clip-path="url(#y_ticker)">
|
||||||
<template v-for="tick of lines[0]" :key="tick">
|
<template v-for="tick of lines[0]" :key="tick">
|
||||||
<polyline
|
<polyline
|
||||||
@@ -180,8 +181,7 @@ const lines = computed(() => {
|
|||||||
text_offset
|
text_offset
|
||||||
"
|
"
|
||||||
:y="y_map(tick)"
|
:y="y_map(tick)"
|
||||||
>{{ tick.toFixed(2) }}</text
|
><NumericText :value="tick" :max_width="7"></NumericText></text>
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
</g>
|
</g>
|
||||||
<g class="major_tick" clip-path="url(#y_ticker)">
|
<g class="major_tick" clip-path="url(#y_ticker)">
|
||||||
@@ -196,7 +196,7 @@ const lines = computed(() => {
|
|||||||
text_offset
|
text_offset
|
||||||
"
|
"
|
||||||
:y="y_map(tick)"
|
:y="y_map(tick)"
|
||||||
>{{ tick.toFixed(1) }}</text
|
><NumericText :value="tick" :max_width="6"></NumericText></text
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</g>
|
</g>
|
||||||
@@ -212,12 +212,14 @@ const lines = computed(() => {
|
|||||||
text_offset
|
text_offset
|
||||||
"
|
"
|
||||||
:y="y_map(tick)"
|
:y="y_map(tick)"
|
||||||
>{{ tick.toFixed(0) }}</text
|
>
|
||||||
|
<NumericText :value="tick" :max_width="5"></NumericText>
|
||||||
|
</text
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</g>
|
</g>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="side == AxisSide.Left">
|
<template v-if="side == GraphSide.Left">
|
||||||
<g class="minor_tick" clip-path="url(#y_ticker)">
|
<g class="minor_tick" clip-path="url(#y_ticker)">
|
||||||
<template v-for="tick of lines[0]" :key="tick">
|
<template v-for="tick of lines[0]" :key="tick">
|
||||||
<polyline
|
<polyline
|
||||||
@@ -230,8 +232,7 @@ const lines = computed(() => {
|
|||||||
text_offset
|
text_offset
|
||||||
"
|
"
|
||||||
:y="y_map(tick)"
|
:y="y_map(tick)"
|
||||||
>{{ tick.toFixed(2) }}</text
|
><NumericText :value="tick" :max_width="7"></NumericText></text>
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
</g>
|
</g>
|
||||||
<g class="major_tick" clip-path="url(#y_ticker)">
|
<g class="major_tick" clip-path="url(#y_ticker)">
|
||||||
@@ -246,8 +247,7 @@ const lines = computed(() => {
|
|||||||
text_offset
|
text_offset
|
||||||
"
|
"
|
||||||
:y="y_map(tick)"
|
:y="y_map(tick)"
|
||||||
>{{ tick.toFixed(1) }}</text
|
><NumericText :value="tick" :max_width="6"></NumericText></text>
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
</g>
|
</g>
|
||||||
<g class="grid_tick" clip-path="url(#y_ticker)">
|
<g class="grid_tick" clip-path="url(#y_ticker)">
|
||||||
@@ -262,8 +262,7 @@ const lines = computed(() => {
|
|||||||
text_offset
|
text_offset
|
||||||
"
|
"
|
||||||
:y="y_map(tick)"
|
:y="y_map(tick)"
|
||||||
>{{ tick.toFixed(0) }}</text
|
><NumericText :value="tick" :max_width="5"></NumericText></text>
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
</g>
|
</g>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
65
frontend/src/components/NumericText.vue
Normal file
65
frontend/src/components/NumericText.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, type Ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
value: number;
|
||||||
|
max_width: number;
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update', value: string): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const display_value = computed(() => {
|
||||||
|
if (props.value == 0) {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
let precision = props.value.toPrecision(props.max_width - 3);
|
||||||
|
// Chop off the last character as long as it is a 0
|
||||||
|
while (precision.length > 0 && precision.charAt(precision.length - 1) == '0') {
|
||||||
|
precision = precision.substring(0, precision.length - 1);
|
||||||
|
}
|
||||||
|
if (precision.length > 0 && precision.charAt(precision.length - 1) == '.') {
|
||||||
|
precision = precision.substring(0, precision.length - 1);
|
||||||
|
}
|
||||||
|
if (precision.includes("e")) {
|
||||||
|
let fixed = props.value.toFixed(props.max_width - 4);
|
||||||
|
// Chop off the last character as long as it is a 0
|
||||||
|
while (fixed.length > 0 && fixed.charAt(fixed.length - 1) == '0') {
|
||||||
|
fixed = fixed.substring(0, fixed.length - 1);
|
||||||
|
}
|
||||||
|
if (fixed.length > 0 && fixed.charAt(fixed.length - 1) == '.') {
|
||||||
|
fixed = fixed.substring(0, fixed.length - 1);
|
||||||
|
}
|
||||||
|
if (fixed.length <= props.max_width) {
|
||||||
|
return fixed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (precision.length > props.max_width) {
|
||||||
|
const initial_exponential = props.value.toExponential(props.max_width - 4);
|
||||||
|
const parts = initial_exponential.split('e');
|
||||||
|
let left = parts[0];
|
||||||
|
// Chop off the last character as long as it is a 0
|
||||||
|
while (left.length > 0 && left.charAt(left.length - 1) == '0') {
|
||||||
|
left = left.substring(0, left.length - 1);
|
||||||
|
}
|
||||||
|
if (left.length > 0 && left.charAt(left.length - 1) == '.') {
|
||||||
|
left = left.substring(0, left.length - 1);
|
||||||
|
}
|
||||||
|
let right = parts[1];
|
||||||
|
return left + "e" + right;
|
||||||
|
}
|
||||||
|
return precision;
|
||||||
|
});
|
||||||
|
watch([display_value], ([display_str]) => {
|
||||||
|
emit('update', display_str);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{ display_value }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -8,9 +8,11 @@ const props = defineProps<{
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
border_left_right?: number;
|
|
||||||
border_top_bottom?: number;
|
|
||||||
utc?: boolean;
|
utc?: boolean;
|
||||||
|
left_axis?: boolean;
|
||||||
|
right_axis?: boolean;
|
||||||
|
hide_time_labels?: boolean;
|
||||||
|
hide_time_ticks?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const width = computed(() => {
|
const width = computed(() => {
|
||||||
@@ -60,8 +62,10 @@ const time_lines = [
|
|||||||
time_lines.reverse();
|
time_lines.reverse();
|
||||||
const text_offset = computed(() => 5);
|
const text_offset = computed(() => 5);
|
||||||
|
|
||||||
const border_left_right = computed(() => props.border_left_right || 0);
|
const border_left = computed(() => props.left_axis ? 96 : 0);
|
||||||
const border_top_bottom = computed(() => props.border_top_bottom || 0);
|
const border_right = computed(() => props.right_axis ? 80 : 0);
|
||||||
|
const border_top = computed(() => 6);
|
||||||
|
const border_bottom = computed(() => props.hide_time_labels ? 6 : 24);
|
||||||
|
|
||||||
const max_x = now;
|
const max_x = now;
|
||||||
const min_x = computed(() => max_x.value - window_duration.value);
|
const min_x = computed(() => max_x.value - window_duration.value);
|
||||||
@@ -69,20 +73,20 @@ const min_x = computed(() => max_x.value - window_duration.value);
|
|||||||
const x_map = (x: number) => {
|
const x_map = (x: number) => {
|
||||||
const diff_x = max_x.value - min_x.value;
|
const diff_x = max_x.value - min_x.value;
|
||||||
return (
|
return (
|
||||||
((width.value - 2 * border_left_right.value) * (x - min_x.value)) /
|
((width.value - border_left.value - border_right.value) * (x - min_x.value)) /
|
||||||
diff_x +
|
diff_x +
|
||||||
border_left_right.value
|
border_left.value
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const telemetry_lines = ref([]);
|
const telemetry_lines = ref([]);
|
||||||
|
|
||||||
provide<GraphData>(GRAPH_DATA, {
|
provide<GraphData>(GRAPH_DATA, {
|
||||||
border_top_bottom: border_top_bottom,
|
border_top: border_top,
|
||||||
min_x: min_x,
|
min_x: min_x,
|
||||||
max_x: now,
|
max_x: now,
|
||||||
width: () => width.value - 2 * border_left_right.value,
|
width: () => width.value - border_left.value - border_right.value,
|
||||||
height: () => height.value - 2 * border_top_bottom.value,
|
height: () => height.value - border_top.value - border_bottom.value,
|
||||||
x_map: x_map,
|
x_map: x_map,
|
||||||
lines: telemetry_lines,
|
lines: telemetry_lines,
|
||||||
max_update_rate: 1000 / 10,
|
max_update_rate: 1000 / 10,
|
||||||
@@ -112,25 +116,25 @@ const lines = computed(() => {
|
|||||||
<defs>
|
<defs>
|
||||||
<clipPath id="content">
|
<clipPath id="content">
|
||||||
<rect
|
<rect
|
||||||
:x="border_left_right"
|
:x="border_left"
|
||||||
:y="border_top_bottom"
|
:y="border_top"
|
||||||
:width="width - border_left_right * 2"
|
:width="width - border_left - border_right"
|
||||||
:height="height - border_top_bottom * 2"
|
:height="height - border_top - border_bottom"
|
||||||
></rect>
|
></rect>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
<clipPath id="y_ticker">
|
<clipPath id="y_ticker">
|
||||||
<rect
|
<rect
|
||||||
:x="0"
|
:x="0"
|
||||||
:y="border_top_bottom"
|
:y="border_top"
|
||||||
:width="width"
|
:width="width"
|
||||||
:height="height - border_top_bottom * 2"
|
:height="height - border_top - border_bottom"
|
||||||
></rect>
|
></rect>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
<clipPath id="x_ticker">
|
<clipPath id="x_ticker">
|
||||||
<rect
|
<rect
|
||||||
:x="border_left_right"
|
:x="border_left"
|
||||||
:y="0"
|
:y="0"
|
||||||
:width="width - border_left_right * 2"
|
:width="width - border_left - border_right"
|
||||||
:height="height"
|
:height="height"
|
||||||
></rect>
|
></rect>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
@@ -138,12 +142,14 @@ const lines = computed(() => {
|
|||||||
<g class="time_tick" clip-path="url(#x_ticker)">
|
<g class="time_tick" clip-path="url(#x_ticker)">
|
||||||
<template v-for="tick of lines" :key="tick">
|
<template v-for="tick of lines" :key="tick">
|
||||||
<polyline
|
<polyline
|
||||||
:points="`${x_map(tick)},${border_top_bottom} ${x_map(tick)},${height - border_top_bottom}`"
|
v-if="!hide_time_ticks"
|
||||||
|
:points="`${x_map(tick)},${border_top} ${x_map(tick)},${height - border_bottom}`"
|
||||||
></polyline>
|
></polyline>
|
||||||
<TimeText
|
<TimeText
|
||||||
|
v-if="!hide_time_labels"
|
||||||
class="bottom_edge"
|
class="bottom_edge"
|
||||||
:x="x_map(tick)"
|
:x="x_map(tick)"
|
||||||
:y="height - border_top_bottom + text_offset"
|
:y="height - border_bottom + text_offset"
|
||||||
:timestamp="tick"
|
:timestamp="tick"
|
||||||
:utc="props.utc"
|
:utc="props.utc"
|
||||||
:show_millis="line_duration < 1000"
|
:show_millis="line_duration < 1000"
|
||||||
@@ -167,4 +173,5 @@ const lines = computed(() => {
|
|||||||
stroke: variables.$time-tick;
|
stroke: variables.$time-tick;
|
||||||
fill: variables.$time-tick;
|
fill: variables.$time-tick;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, useTemplateRef, watch } from 'vue';
|
import { computed, ref, useTemplateRef, watch } from 'vue';
|
||||||
|
import NumericText from '@/components/NumericText.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
x: number;
|
x: number;
|
||||||
@@ -12,10 +13,7 @@ const y_offset = computed(() => 9);
|
|||||||
|
|
||||||
const labelRef = useTemplateRef<SVGTextElement>('label-ref');
|
const labelRef = useTemplateRef<SVGTextElement>('label-ref');
|
||||||
|
|
||||||
const value_text = computed(() => {
|
const value_text = ref("");
|
||||||
return props.value.toFixed(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
const label_width = ref(0);
|
const label_width = ref(0);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -27,6 +25,10 @@ watch(
|
|||||||
flush: 'post',
|
flush: 'post',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function update_value_text(text: string) {
|
||||||
|
value_text.value = text;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -37,7 +39,7 @@ watch(
|
|||||||
:height="16 + background_offset * 2"
|
:height="16 + background_offset * 2"
|
||||||
></rect>
|
></rect>
|
||||||
<text ref="label-ref" :x="x" :y="y">
|
<text ref="label-ref" :x="x" :y="y">
|
||||||
{{ value_text }}
|
<NumericText :value="value" :max_width="6" @update="update_value_text"></NumericText>
|
||||||
</text>
|
</text>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
export enum AxisSide {
|
|
||||||
Right,
|
|
||||||
Left,
|
|
||||||
Hidden,
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AxisType {
|
export enum AxisType {
|
||||||
Linear,
|
Linear,
|
||||||
Logarithmic,
|
Logarithmic,
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import type { MaybeRefOrGetter, Ref } from 'vue';
|
import type { MaybeRefOrGetter, Ref } from 'vue';
|
||||||
|
|
||||||
|
export enum GraphSide {
|
||||||
|
Right,
|
||||||
|
Left,
|
||||||
|
Hidden,
|
||||||
|
}
|
||||||
|
|
||||||
export const GRAPH_DATA = Symbol();
|
export const GRAPH_DATA = Symbol();
|
||||||
|
|
||||||
export interface GraphData {
|
export interface GraphData {
|
||||||
border_top_bottom: MaybeRefOrGetter<number>;
|
border_top: MaybeRefOrGetter<number>;
|
||||||
min_x: MaybeRefOrGetter<number>;
|
min_x: MaybeRefOrGetter<number>;
|
||||||
max_x: MaybeRefOrGetter<number>;
|
max_x: MaybeRefOrGetter<number>;
|
||||||
width: MaybeRefOrGetter<number>;
|
width: MaybeRefOrGetter<number>;
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ provide(WEBSOCKET_SYMBOL, websocket);
|
|||||||
<Graph
|
<Graph
|
||||||
:width="800"
|
:width="800"
|
||||||
:height="400"
|
:height="400"
|
||||||
:border_top_bottom="24"
|
:right_axis="true"
|
||||||
:border_left_right="128"
|
|
||||||
>
|
>
|
||||||
<Axis>
|
<Axis>
|
||||||
<Line data="simple_producer/time_offset"></Line>
|
<Line data="simple_producer/time_offset"></Line>
|
||||||
@@ -26,9 +25,8 @@ provide(WEBSOCKET_SYMBOL, websocket);
|
|||||||
<Graph
|
<Graph
|
||||||
:width="800"
|
:width="800"
|
||||||
:height="400"
|
:height="400"
|
||||||
:border_top_bottom="24"
|
|
||||||
:border_left_right="128"
|
|
||||||
:duration="60 * 1000 * 10"
|
:duration="60 * 1000 * 10"
|
||||||
|
:right_axis="true"
|
||||||
>
|
>
|
||||||
<Axis>
|
<Axis>
|
||||||
<Line
|
<Line
|
||||||
@@ -68,16 +66,12 @@ provide(WEBSOCKET_SYMBOL, websocket);
|
|||||||
<Graph
|
<Graph
|
||||||
:width="800"
|
:width="800"
|
||||||
:height="400"
|
:height="400"
|
||||||
:border_top_bottom="24"
|
|
||||||
:border_left_right="128"
|
|
||||||
:duration="5 * 1000"
|
:duration="5 * 1000"
|
||||||
>
|
>
|
||||||
</Graph>
|
</Graph>
|
||||||
<Graph
|
<Graph
|
||||||
:width="800"
|
:width="800"
|
||||||
:height="400"
|
:height="400"
|
||||||
:border_top_bottom="24"
|
|
||||||
:border_left_right="128"
|
|
||||||
:duration="2 * 1000"
|
:duration="2 * 1000"
|
||||||
>
|
>
|
||||||
</Graph>
|
</Graph>
|
||||||
|
|||||||
Reference in New Issue
Block a user