improve numeric labeling
This commit is contained in:
@@ -1,18 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, provide, ref, toValue, watch } from 'vue';
|
||||
import { AXIS_DATA, type AxisData, AxisSide, AxisType } from '@/graph/axis';
|
||||
import { GRAPH_DATA, type GraphData } from '@/graph/graph';
|
||||
import { AXIS_DATA, type AxisData, AxisType } from '@/graph/axis';
|
||||
import { GRAPH_DATA, type GraphData, GraphSide } from '@/graph/graph';
|
||||
import NumericText from '@/components/NumericText.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
y_limits?: [number, number];
|
||||
side?: AxisSide;
|
||||
side?: GraphSide;
|
||||
type?: AxisType;
|
||||
}>();
|
||||
|
||||
const minor_tick_length = computed(() => 4);
|
||||
const major_tick_length = computed(() => 8);
|
||||
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 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 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 min_value = toValue(min_y_value);
|
||||
let y_value = y;
|
||||
@@ -111,7 +112,7 @@ const y_map = (y: number) => {
|
||||
y_value = Math.log(y_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, {
|
||||
@@ -167,7 +168,7 @@ const lines = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="side == AxisSide.Right">
|
||||
<template v-if="side == GraphSide.Right">
|
||||
<g class="minor_tick" clip-path="url(#y_ticker)">
|
||||
<template v-for="tick of lines[0]" :key="tick">
|
||||
<polyline
|
||||
@@ -180,8 +181,7 @@ const lines = computed(() => {
|
||||
text_offset
|
||||
"
|
||||
:y="y_map(tick)"
|
||||
>{{ tick.toFixed(2) }}</text
|
||||
>
|
||||
><NumericText :value="tick" :max_width="7"></NumericText></text>
|
||||
</template>
|
||||
</g>
|
||||
<g class="major_tick" clip-path="url(#y_ticker)">
|
||||
@@ -196,7 +196,7 @@ const lines = computed(() => {
|
||||
text_offset
|
||||
"
|
||||
:y="y_map(tick)"
|
||||
>{{ tick.toFixed(1) }}</text
|
||||
><NumericText :value="tick" :max_width="6"></NumericText></text
|
||||
>
|
||||
</template>
|
||||
</g>
|
||||
@@ -212,12 +212,14 @@ const lines = computed(() => {
|
||||
text_offset
|
||||
"
|
||||
:y="y_map(tick)"
|
||||
>{{ tick.toFixed(0) }}</text
|
||||
>
|
||||
<NumericText :value="tick" :max_width="5"></NumericText>
|
||||
</text
|
||||
>
|
||||
</template>
|
||||
</g>
|
||||
</template>
|
||||
<template v-if="side == AxisSide.Left">
|
||||
<template v-if="side == GraphSide.Left">
|
||||
<g class="minor_tick" clip-path="url(#y_ticker)">
|
||||
<template v-for="tick of lines[0]" :key="tick">
|
||||
<polyline
|
||||
@@ -230,8 +232,7 @@ const lines = computed(() => {
|
||||
text_offset
|
||||
"
|
||||
:y="y_map(tick)"
|
||||
>{{ tick.toFixed(2) }}</text
|
||||
>
|
||||
><NumericText :value="tick" :max_width="7"></NumericText></text>
|
||||
</template>
|
||||
</g>
|
||||
<g class="major_tick" clip-path="url(#y_ticker)">
|
||||
@@ -246,8 +247,7 @@ const lines = computed(() => {
|
||||
text_offset
|
||||
"
|
||||
:y="y_map(tick)"
|
||||
>{{ tick.toFixed(1) }}</text
|
||||
>
|
||||
><NumericText :value="tick" :max_width="6"></NumericText></text>
|
||||
</template>
|
||||
</g>
|
||||
<g class="grid_tick" clip-path="url(#y_ticker)">
|
||||
@@ -262,8 +262,7 @@ const lines = computed(() => {
|
||||
text_offset
|
||||
"
|
||||
:y="y_map(tick)"
|
||||
>{{ tick.toFixed(0) }}</text
|
||||
>
|
||||
><NumericText :value="tick" :max_width="5"></NumericText></text>
|
||||
</template>
|
||||
</g>
|
||||
</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;
|
||||
height: number;
|
||||
duration?: number;
|
||||
border_left_right?: number;
|
||||
border_top_bottom?: number;
|
||||
utc?: boolean;
|
||||
left_axis?: boolean;
|
||||
right_axis?: boolean;
|
||||
hide_time_labels?: boolean;
|
||||
hide_time_ticks?: boolean;
|
||||
}>();
|
||||
|
||||
const width = computed(() => {
|
||||
@@ -60,8 +62,10 @@ const time_lines = [
|
||||
time_lines.reverse();
|
||||
const text_offset = computed(() => 5);
|
||||
|
||||
const border_left_right = computed(() => props.border_left_right || 0);
|
||||
const border_top_bottom = computed(() => props.border_top_bottom || 0);
|
||||
const border_left = computed(() => props.left_axis ? 96 : 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 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 diff_x = max_x.value - min_x.value;
|
||||
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 +
|
||||
border_left_right.value
|
||||
border_left.value
|
||||
);
|
||||
};
|
||||
|
||||
const telemetry_lines = ref([]);
|
||||
|
||||
provide<GraphData>(GRAPH_DATA, {
|
||||
border_top_bottom: border_top_bottom,
|
||||
border_top: border_top,
|
||||
min_x: min_x,
|
||||
max_x: now,
|
||||
width: () => width.value - 2 * border_left_right.value,
|
||||
height: () => height.value - 2 * border_top_bottom.value,
|
||||
width: () => width.value - border_left.value - border_right.value,
|
||||
height: () => height.value - border_top.value - border_bottom.value,
|
||||
x_map: x_map,
|
||||
lines: telemetry_lines,
|
||||
max_update_rate: 1000 / 10,
|
||||
@@ -112,25 +116,25 @@ const lines = computed(() => {
|
||||
<defs>
|
||||
<clipPath id="content">
|
||||
<rect
|
||||
:x="border_left_right"
|
||||
:y="border_top_bottom"
|
||||
:width="width - border_left_right * 2"
|
||||
:height="height - border_top_bottom * 2"
|
||||
:x="border_left"
|
||||
:y="border_top"
|
||||
:width="width - border_left - border_right"
|
||||
:height="height - border_top - border_bottom"
|
||||
></rect>
|
||||
</clipPath>
|
||||
<clipPath id="y_ticker">
|
||||
<rect
|
||||
:x="0"
|
||||
:y="border_top_bottom"
|
||||
:y="border_top"
|
||||
:width="width"
|
||||
:height="height - border_top_bottom * 2"
|
||||
:height="height - border_top - border_bottom"
|
||||
></rect>
|
||||
</clipPath>
|
||||
<clipPath id="x_ticker">
|
||||
<rect
|
||||
:x="border_left_right"
|
||||
:x="border_left"
|
||||
:y="0"
|
||||
:width="width - border_left_right * 2"
|
||||
:width="width - border_left - border_right"
|
||||
:height="height"
|
||||
></rect>
|
||||
</clipPath>
|
||||
@@ -138,12 +142,14 @@ const lines = computed(() => {
|
||||
<g class="time_tick" clip-path="url(#x_ticker)">
|
||||
<template v-for="tick of lines" :key="tick">
|
||||
<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>
|
||||
<TimeText
|
||||
v-if="!hide_time_labels"
|
||||
class="bottom_edge"
|
||||
:x="x_map(tick)"
|
||||
:y="height - border_top_bottom + text_offset"
|
||||
:y="height - border_bottom + text_offset"
|
||||
:timestamp="tick"
|
||||
:utc="props.utc"
|
||||
:show_millis="line_duration < 1000"
|
||||
@@ -167,4 +173,5 @@ const lines = computed(() => {
|
||||
stroke: variables.$time-tick;
|
||||
fill: variables.$time-tick;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, useTemplateRef, watch } from 'vue';
|
||||
import NumericText from '@/components/NumericText.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
x: number;
|
||||
@@ -12,10 +13,7 @@ const y_offset = computed(() => 9);
|
||||
|
||||
const labelRef = useTemplateRef<SVGTextElement>('label-ref');
|
||||
|
||||
const value_text = computed(() => {
|
||||
return props.value.toFixed(2);
|
||||
});
|
||||
|
||||
const value_text = ref("");
|
||||
const label_width = ref(0);
|
||||
|
||||
watch(
|
||||
@@ -27,6 +25,10 @@ watch(
|
||||
flush: 'post',
|
||||
},
|
||||
);
|
||||
|
||||
function update_value_text(text: string) {
|
||||
value_text.value = text;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -37,7 +39,7 @@ watch(
|
||||
:height="16 + background_offset * 2"
|
||||
></rect>
|
||||
<text ref="label-ref" :x="x" :y="y">
|
||||
{{ value_text }}
|
||||
<NumericText :value="value" :max_width="6" @update="update_value_text"></NumericText>
|
||||
</text>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user