frontend/src/components/ChartBlock.vue

206 lines
4.5 KiB
Vue
Raw Normal View History

2025-09-27 19:42:09 +03:00
<script setup>
import { ref, watch } from 'vue'
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
LineElement,
PointElement,
CategoryScale,
TimeScale,
LinearScale,
} from 'chart.js'
import { Line } from 'vue-chartjs'
import 'chartjs-adapter-date-fns'
ChartJS.register(
Title,
Tooltip,
Legend,
LineElement,
PointElement,
CategoryScale,
TimeScale,
LinearScale,
)
const props = defineProps({
chartsData: {
2025-10-01 11:02:31 +03:00
type: [Object, null],
2025-09-27 19:42:09 +03:00
required: true,
2025-10-01 11:02:31 +03:00
default: null,
2025-09-27 19:42:09 +03:00
},
})
const originColors = {
2025-10-06 21:27:09 +03:00
surugaya: '#2d3081',
mandarake: '#924646',
2025-09-27 19:42:09 +03:00
}
const chartData = ref({
datasets: [],
})
2025-10-01 11:02:31 +03:00
const showPlaceholder = ref(false)
const placeholderMessage = ref('')
function transformToChartJSSeries(payload) {
const datasets = []
for (const origin of payload.origins) {
if (!origin.Prices || origin.Prices.length === 0) continue
const data = origin.Prices.filter((p) => p.value != null)
.map((p) => {
let y = p.value
if (typeof y === 'string') {
y = parseFloat(y)
}
const x =
typeof p.created_at === 'number' ? p.created_at * 1000 : new Date(p.created_at).getTime()
return {
x,
y: typeof y === 'number' && !isNaN(y) ? y : null,
}
})
.filter((p) => p.y !== null)
.sort((a, b) => a.x - b.x)
if (data.length === 0) continue
const color = originColors[origin.origin] || '#000000'
datasets.push({
label: origin.origin,
data,
borderColor: color,
backgroundColor: color + '20',
fill: false,
pointRadius: 4,
pointHoverRadius: 6,
type: 'line',
})
}
return datasets
}
2025-09-27 19:42:09 +03:00
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
animation: false,
plugins: {
legend: { display: true },
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
title: (context) => {
const date = new Date(context[0].parsed.x)
return date.toLocaleString('ru-RU', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
},
},
},
},
scales: {
x: {
type: 'time',
time: {
tooltipFormat: 'dd.MM.yyyy HH:mm',
unit: 'day',
displayFormats: { day: 'dd MMM' },
},
title: { display: true, text: 'Date' },
},
y: {
title: { display: true, text: 'Price' },
ticks: { precision: 0 },
},
},
elements: {
line: { borderWidth: 2, tension: 0, spanGaps: true },
point: { radius: 4, hoverRadius: 6 },
},
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false,
},
}
2025-10-01 11:02:31 +03:00
watch(
() => props.chartsData,
(newData) => {
// Сброс состояния
showPlaceholder.value = false
placeholderMessage.value = ''
2025-09-27 19:42:09 +03:00
2025-10-01 11:02:31 +03:00
// Случай 1: null или undefined
if (newData === null || newData === undefined) {
showPlaceholder.value = true
placeholderMessage.value = 'No prices found'
chartData.value = { datasets: [] }
return
}
2025-09-27 19:42:09 +03:00
2025-10-01 11:02:31 +03:00
// Случай 2: явная ошибка в объекте
if (typeof newData === 'object' && newData.error) {
showPlaceholder.value = true
placeholderMessage.value = newData.error
chartData.value = { datasets: [] }
return
}
2025-09-27 19:42:09 +03:00
2025-10-01 11:02:31 +03:00
// Случай 3: нормальный объект — пробуем преобразовать
let datasets = []
if (newData.origins && Array.isArray(newData.origins)) {
datasets = transformToChartJSSeries(newData)
}
2025-09-27 19:42:09 +03:00
2025-10-01 11:02:31 +03:00
// 🔥 Ключевое изменение: проверяем, есть ли хоть один датасет
if (datasets.length === 0) {
showPlaceholder.value = true
placeholderMessage.value = 'No prices found'
chartData.value = { datasets: [] }
} else {
showPlaceholder.value = false
2025-09-27 19:42:09 +03:00
chartData.value = { datasets }
}
},
2025-10-01 11:02:31 +03:00
{ immediate: true }
2025-09-27 19:42:09 +03:00
)
</script>
<template>
2025-10-01 11:02:31 +03:00
<div class="chart-container" style="height: 100%; width: 100%">
<div v-if="showPlaceholder" class="error-placeholder">
{{ placeholderMessage }}
</div>
<Line v-else :data="chartData" :options="chartOptions" style="height: 100%; width: 100%" />
</div>
2025-09-27 19:42:09 +03:00
</template>
<style scoped>
2025-10-01 11:02:31 +03:00
.chart-container {
position: relative;
2025-09-27 19:42:09 +03:00
min-width: 80%;
}
2025-10-01 11:02:31 +03:00
.error-placeholder {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
color: #666;
font-size: 1.2rem;
text-align: center;
}
2025-09-27 19:42:09 +03:00
</style>