102 lines
2.2 KiB
Vue
102 lines
2.2 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
|
import uPlot from 'uplot'
|
|
import 'uplot/dist/uPlot.min.css'
|
|
|
|
export interface TimeSeriesSeries {
|
|
label: string
|
|
color: string
|
|
}
|
|
|
|
const props = withDefaults(defineProps<{
|
|
/** Array of series configs (label + color) */
|
|
series: TimeSeriesSeries[]
|
|
/** Data: [timestamps[], series1[], series2[], ...] */
|
|
data: uPlot.AlignedData
|
|
/** Chart title (optional) */
|
|
title?: string
|
|
/** Stacked area mode */
|
|
stacked?: boolean
|
|
}>(), {
|
|
stacked: false,
|
|
})
|
|
|
|
const container = ref<HTMLElement | null>(null)
|
|
let chart: uPlot | null = null
|
|
|
|
function buildOpts(): uPlot.Options {
|
|
const seriesOpts: uPlot.Series[] = [
|
|
{ label: 'Time' },
|
|
...props.series.map((s) => ({
|
|
label: s.label,
|
|
stroke: s.color,
|
|
fill: props.stacked ? s.color + '40' : undefined,
|
|
width: 2,
|
|
})),
|
|
]
|
|
|
|
return {
|
|
width: container.value?.clientWidth ?? 400,
|
|
height: container.value?.clientHeight ?? 200,
|
|
series: seriesOpts,
|
|
axes: [
|
|
{ stroke: '#555568', grid: { stroke: '#2e2e3822' } },
|
|
{ stroke: '#555568', grid: { stroke: '#2e2e3822' } },
|
|
],
|
|
cursor: { show: true },
|
|
legend: { show: true },
|
|
}
|
|
}
|
|
|
|
function createChart() {
|
|
if (!container.value) return
|
|
if (chart) chart.destroy()
|
|
chart = new uPlot(buildOpts(), props.data, container.value)
|
|
}
|
|
|
|
function resize() {
|
|
if (!chart || !container.value) return
|
|
chart.setSize({
|
|
width: container.value.clientWidth,
|
|
height: container.value.clientHeight,
|
|
})
|
|
}
|
|
|
|
watch(() => props.data, (newData) => {
|
|
if (chart) {
|
|
chart.setData(newData)
|
|
} else {
|
|
nextTick(createChart)
|
|
}
|
|
}, { deep: true })
|
|
|
|
onMounted(() => {
|
|
nextTick(createChart)
|
|
const observer = new ResizeObserver(resize)
|
|
if (container.value) observer.observe(container.value)
|
|
onUnmounted(() => {
|
|
observer.disconnect()
|
|
chart?.destroy()
|
|
chart = null
|
|
})
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div ref="container" class="timeseries-renderer" />
|
|
</template>
|
|
|
|
<style scoped>
|
|
.timeseries-renderer {
|
|
width: 100%;
|
|
height: 100%;
|
|
min-height: 150px;
|
|
}
|
|
|
|
.timeseries-renderer :deep(.u-legend) {
|
|
font-family: var(--font-mono);
|
|
font-size: var(--font-size-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
</style>
|