This commit is contained in:
2026-03-23 15:18:23 -03:00
parent 5ed876d694
commit b57da622cb
17 changed files with 554 additions and 103 deletions

View File

@@ -10,3 +10,4 @@ export { default as LayoutGrid } from './components/LayoutGrid.vue'
// Renderers
export { default as LogRenderer } from './renderers/LogRenderer.vue'
export { default as TimeSeriesRenderer } from './renderers/TimeSeriesRenderer.vue'

View File

@@ -0,0 +1,101 @@
<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>