210 lines
5.6 KiB
Vue
210 lines
5.6 KiB
Vue
<script setup lang="ts">
|
|
import { computed, provide, ref } from 'vue';
|
|
import { useNow } from '@/composables/ticker';
|
|
import { GRAPH_DATA, type GraphData } from '@/graph/graph';
|
|
|
|
const props = defineProps<{
|
|
width: number;
|
|
height: number;
|
|
border_left_right?: number;
|
|
border_top_bottom?: number;
|
|
utc?: boolean;
|
|
}>();
|
|
|
|
const width = computed(() => {
|
|
return props.width;
|
|
});
|
|
|
|
const height = computed(() => {
|
|
return props.height;
|
|
});
|
|
|
|
const now = useNow(33);
|
|
const window_duration = 10 * 1000; // 10 seconds
|
|
|
|
const time_lines = [
|
|
1, // 1ms
|
|
2, // 2ms
|
|
5, // 5ms
|
|
10, // 10ms
|
|
20, // 20ms
|
|
50, // 50ms
|
|
100, // 100ms
|
|
200, // 200ms
|
|
500, // 500ms
|
|
1000, // 1s
|
|
2000, // 2s
|
|
5000, // 5s
|
|
10000, // 10s
|
|
30000, // 30s
|
|
60000, // 1m
|
|
300000, // 5m
|
|
6000000, // 10m
|
|
18000000, // 30m
|
|
36000000, // 1h
|
|
72000000, // 2h
|
|
144000000, // 4h
|
|
216000000, // 6h
|
|
432000000, // 12h
|
|
864000000, // 1d
|
|
1728000000, // 2d
|
|
6048000000, // 1w
|
|
];
|
|
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 max_x = now;
|
|
const min_x = computed(() => max_x.value - window_duration);
|
|
|
|
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)) /
|
|
diff_x +
|
|
border_left_right.value
|
|
);
|
|
};
|
|
|
|
const telemetry_lines = ref([]);
|
|
|
|
provide<GraphData>(GRAPH_DATA, {
|
|
border_top_bottom: border_top_bottom,
|
|
min_x: min_x,
|
|
max_x: now,
|
|
width: () => width.value - 2 * border_left_right.value,
|
|
height: () => height.value - 2 * border_top_bottom.value,
|
|
x_map: x_map,
|
|
lines: telemetry_lines,
|
|
});
|
|
|
|
const duration = computed(() => {
|
|
const diff_x = max_x.value - min_x.value;
|
|
return time_lines.find((duration) => diff_x / duration >= 3)!;
|
|
});
|
|
|
|
const lines = computed(() => {
|
|
const result = [];
|
|
for (
|
|
let i = Math.ceil(max_x.value / duration.value);
|
|
i >= Math.ceil(min_x.value / duration.value) - 5;
|
|
i--
|
|
) {
|
|
const x = i * duration.value;
|
|
result.push(x);
|
|
}
|
|
return result;
|
|
});
|
|
|
|
function getDateString(date: Date) {
|
|
const year = props.utc ? date.getUTCFullYear() : date.getFullYear();
|
|
const month = (
|
|
(props.utc ? date.getMonth() : date.getMonth()) + 1
|
|
).toLocaleString('en-US', {
|
|
minimumIntegerDigits: 2,
|
|
useGrouping: false,
|
|
maximumFractionDigits: 0,
|
|
});
|
|
const day = (props.utc ? date.getUTCDate() : date.getDate()).toLocaleString(
|
|
'en-US',
|
|
{
|
|
minimumIntegerDigits: 2,
|
|
useGrouping: false,
|
|
maximumFractionDigits: 0,
|
|
},
|
|
);
|
|
const hour = (
|
|
props.utc ? date.getUTCHours() : date.getHours()
|
|
).toLocaleString('en-US', {
|
|
minimumIntegerDigits: 2,
|
|
useGrouping: false,
|
|
maximumFractionDigits: 0,
|
|
});
|
|
const minute = (
|
|
props.utc ? date.getUTCMinutes() : date.getMinutes()
|
|
).toLocaleString('en-US', {
|
|
minimumIntegerDigits: 2,
|
|
useGrouping: false,
|
|
maximumFractionDigits: 0,
|
|
});
|
|
const second = (
|
|
props.utc ? date.getUTCSeconds() : date.getSeconds()
|
|
).toLocaleString('en-US', {
|
|
minimumIntegerDigits: 2,
|
|
useGrouping: false,
|
|
maximumFractionDigits: 0,
|
|
});
|
|
const milliseconds = (
|
|
props.utc ? date.getUTCMilliseconds() : date.getMilliseconds()
|
|
).toLocaleString('en-US', {
|
|
minimumIntegerDigits: 3,
|
|
useGrouping: false,
|
|
maximumFractionDigits: 0,
|
|
});
|
|
return `${year}/${month}/${day} ${hour}:${minute}:${second}${duration.value < 1000 ? `.${milliseconds}` : ''}${props.utc ? 'Z' : ''}`;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<svg ref="svg_graph" class="graph" :width="width" :height="height">
|
|
<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"
|
|
></rect>
|
|
</clipPath>
|
|
<clipPath id="y_ticker">
|
|
<rect
|
|
:x="0"
|
|
:y="border_top_bottom"
|
|
:width="width"
|
|
:height="height - border_top_bottom * 2"
|
|
></rect>
|
|
</clipPath>
|
|
<clipPath id="x_ticker">
|
|
<rect
|
|
:x="border_left_right"
|
|
:y="0"
|
|
:width="width - border_left_right * 2"
|
|
:height="height"
|
|
></rect>
|
|
</clipPath>
|
|
</defs>
|
|
<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}`"
|
|
></polyline>
|
|
<text
|
|
class="bottom_edge"
|
|
:x="x_map(tick)"
|
|
:y="height - border_top_bottom + text_offset"
|
|
>
|
|
{{ getDateString(new Date(tick)) }}
|
|
</text>
|
|
</template>
|
|
</g>
|
|
<slot></slot>
|
|
</svg>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
@use '@/assets/variables';
|
|
|
|
.bottom_edge {
|
|
text-anchor: middle;
|
|
alignment-baseline: text-before-edge;
|
|
font-family: variables.$text-font;
|
|
}
|
|
|
|
.time_tick {
|
|
stroke: variables.$time-tick;
|
|
fill: variables.$time-tick;
|
|
}
|
|
</style>
|