<script setup>
// Обёртка chart.js: линейный график в стиле кита. Одна ось, тонкие линии,
// hover-тултип по индексу, легенда только при >=2 сериях (одиночную
// серию называет заголовок карточки).
import { Chart } from 'chart.js/auto'
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { INK } from '../chartTheme.js'
const props = defineProps({
labels: { type: Array, default: () => [] },
// [{ label, data: number[], color, fill? }]
datasets: { type: Array, default: () => [] },
yMax: { type: Number, default: null },
yUnit: { type: String, default: '' },
height: { type: Number, default: 220 },
})
const canvas = ref(null)
let chart = null
onMounted(() => {
chart = new Chart(canvas.value, {
type: 'line',
data: {
labels: props.labels,
datasets: props.datasets.map((ds) => ({
label: ds.label,
data: ds.data,
borderColor: ds.color,
backgroundColor: ds.color + '26',
borderWidth: 2,
pointRadius: 0,
pointHoverRadius: 5,
fill: Boolean(ds.fill),
tension: 0.25,
})),
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: {
display: props.datasets.length > 1,
labels: { color: INK.muted, usePointStyle: true, boxWidth: 6, boxHeight: 6 },
},
tooltip: {
backgroundColor: INK.panel,
borderColor: INK.border,
borderWidth: 1,
titleColor: INK.text,
bodyColor: INK.text,
padding: 10,
callbacks: {
label: (ctx) => ` ${ctx.dataset.label}: ${round(ctx.parsed.y)}${props.yUnit}`,
},
},
},
scales: {
x: {
ticks: { color: INK.muted, maxTicksLimit: 8, maxRotation: 0 },
grid: { color: INK.grid },
},
y: {
beginAtZero: true,
max: props.yMax ?? undefined,
ticks: {
color: INK.muted,
callback: (v) => `${round(v)}${props.yUnit}`,
},
grid: { color: INK.grid },
},
},
},
})
})
watch(
() => [props.labels, props.datasets],
() => {
if (!chart) return
chart.data.labels = props.labels
props.datasets.forEach((ds, i) => {
if (chart.data.datasets[i]) {
chart.data.datasets[i].label = ds.label
chart.data.datasets[i].data = ds.data
}
})
chart.update('none')
},
{ deep: true },
)
onBeforeUnmount(() => {
chart?.destroy()
chart = null
})
function round(v) {
return Math.abs(v) >= 100 ? Math.round(v) : Math.round(v * 10) / 10
}
</script>
<template>
<div class="chart-box" :style="{ height: height + 'px' }">
<canvas ref="canvas" />
</div>
</template>
<style scoped>
.chart-box {
position: relative;
width: 100%;
}
</style>