<script setup lang="ts">
|
import { computed, ref, watch } from 'vue'
|
import { use } from 'echarts/core'
|
import { LineChart } from 'echarts/charts'
|
import { CanvasRenderer } from 'echarts/renderers'
|
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
|
import VChart from 'vue-echarts'
|
|
interface ConsoleCell {
|
no: string
|
voltage: string
|
temperature: string
|
positive: string
|
negative: string
|
capacity: string
|
current: string
|
}
|
|
interface ProjectMetric {
|
key: 'finalVoltage1' | 'finalVoltage2' | 'dischargeCapacityF'
|
title: string
|
unit: string
|
}
|
|
const props = defineProps<{
|
cells: ConsoleCell[]
|
}>()
|
|
use([CanvasRenderer, LineChart, GridComponent, LegendComponent, TooltipComponent])
|
|
const metrics: ProjectMetric[] = [
|
{ key: 'finalVoltage1', title: '最终电压1', unit: 'V' },
|
{ key: 'finalVoltage2', title: '最终电压2', unit: 'V' },
|
{ key: 'dischargeCapacityF', title: '每循环放电末容量 / F', unit: 'F' },
|
]
|
const palette = ['#3978f6', '#7d42d8', '#f08a3e', '#23a983', '#e15268', '#1b9ac8', '#b5892f', '#6957c8', '#4f8d44', '#d264a3', '#46748e', '#a95d37']
|
const seriesColors = ref<string[]>(props.cells.map((_, index) => palette[index % palette.length]))
|
const cycleLabels = Array.from({ length: 61 }, (_, index) => String(index + 1))
|
const chartUpdateOptions = { notMerge: true, lazyUpdate: false }
|
|
watch(() => props.cells, cells => {
|
seriesColors.value = cells.map((_, index) => seriesColors.value[index] || palette[index % palette.length])
|
}, { deep: true })
|
|
const chartStateKey = computed(() => props.cells.map(cell => cell.no).join('|'))
|
|
function metricBase(cell: ConsoleCell, metric: ProjectMetric) {
|
if (metric.key === 'finalVoltage1') return Number(cell.voltage)
|
if (metric.key === 'finalVoltage2') return Number(cell.positive)
|
return Number(cell.capacity) / 1000
|
}
|
|
function seriesValues(cell: ConsoleCell, metric: ProjectMetric, cellIndex: number) {
|
const base = metricBase(cell, metric)
|
return cycleLabels.map((_, pointIndex) => {
|
const progress = pointIndex / (cycleLabels.length - 1)
|
const cycleWave = Math.sin(progress * Math.PI * 4 + cellIndex * 0.38)
|
const localWave = Math.cos(progress * Math.PI * 9 + cellIndex * 0.2)
|
if (metric.key === 'dischargeCapacityF') {
|
return Number((base * (1 - progress * 0.08) + cycleWave * 0.018 + localWave * 0.006).toFixed(4))
|
}
|
const direction = metric.key === 'finalVoltage1' ? 1 : -1
|
return Number((base + cycleWave * 0.075 + localWave * 0.018 + direction * progress * 0.04).toFixed(4))
|
})
|
}
|
|
function chartOption(metric: ProjectMetric) {
|
const seriesData = props.cells.map((cell, index) => seriesValues(cell, metric, index))
|
const values = seriesData.flat()
|
const dataMin = values.length ? Math.min(...values) : 0
|
const dataMax = values.length ? Math.max(...values) : 1
|
const margin = Math.max((dataMax - dataMin) * 0.15, metric.key === 'dischargeCapacityF' ? 0.03 : 0.08)
|
const createYAxis = (position: 'left' | 'right') => ({
|
type: 'value',
|
name: metric.unit,
|
position,
|
nameLocation: 'middle',
|
nameGap: 42,
|
nameRotate: position === 'left' ? 90 : -90,
|
min: Number((dataMin - margin).toFixed(3)),
|
max: Number((dataMax + margin).toFixed(3)),
|
nameTextStyle: { color: '#8795a3', fontSize: 10 },
|
axisLine: { show: true, lineStyle: { color: '#cbd8e4' } },
|
axisTick: { show: true, lineStyle: { color: '#cbd8e4' } },
|
axisLabel: { color: '#8795a3', fontSize: 10, margin: 8 },
|
splitLine: { show: position === 'left', lineStyle: { color: '#e8eef4' } },
|
})
|
|
return {
|
animation: false,
|
color: seriesColors.value,
|
grid: { top: 62, right: 62, bottom: 48, left: 62 },
|
tooltip: { trigger: 'axis' },
|
legend: {
|
top: 8,
|
left: 12,
|
right: 12,
|
type: 'scroll',
|
icon: 'roundRect',
|
itemWidth: 14,
|
itemHeight: 8,
|
itemGap: 16,
|
textStyle: { fontSize: 10, color: '#52606d' },
|
},
|
xAxis: {
|
type: 'category',
|
boundaryGap: false,
|
name: '循环次数',
|
nameLocation: 'middle',
|
nameGap: 30,
|
data: cycleLabels,
|
axisLine: { lineStyle: { color: '#cbd8e4' } },
|
axisLabel: { color: '#8795a3', fontSize: 10, interval: 9 },
|
nameTextStyle: { color: '#52606d', fontSize: 10 },
|
},
|
yAxis: [createYAxis('left'), createYAxis('right')],
|
series: props.cells.map((cell, index) => ({ name: cell.no, type: 'line', smooth: true, showSymbol: false, lineStyle: { width: 2 }, data: seriesData[index] })),
|
}
|
}
|
</script>
|
|
<template>
|
<section class="project-chart-panel">
|
<article v-for="metric in metrics" :key="metric.key" class="project-chart-card">
|
<header><strong>{{ metric.title }}</strong></header>
|
<VChart :key="`${metric.key}-${chartStateKey}`" class="project-chart" :option="chartOption(metric)" :update-options="chartUpdateOptions" autoresize />
|
</article>
|
</section>
|
</template>
|
|
<style scoped>
|
.project-chart-panel{display:grid;gap:16px;min-width:0}.project-chart-card{min-width:0;border:1px solid #d8e1e9;background:#fff}.project-chart-card>header{height:44px;display:flex;align-items:center;padding:0 18px;border-bottom:1px solid #edf1f5}.project-chart-card>header strong{color:#26333f;font-size:13px}.project-chart{width:100%;height:270px}@media(max-width:680px){.project-chart{height:250px}.project-chart-card>header{padding:0 12px}}
|
</style>
|