You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

295 lines
7.2 KiB

<template>
<div class="device-data-chart">
3 months ago
<v-chart
class="chart"
ref="chartRef"
:option="chartOption"
:autoresize="autoresize"
:loading-options="loadingOpt"
:loading="loading"
@click="onChartClick"
/>
</div>
<LineChartDlg ref="LineChartDlgRef" @on-save="onChangeCoefficient"/>
</template>
<script setup lang="ts">
import Big from 'big.js'
import dayjs from 'dayjs'
import VChart from 'vue-echarts'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import type { EChartsOption, SeriesOption } from 'echarts/types/dist/shared.js'
import { LineChart } from 'echarts/charts'
import LineChartDlg from './line-dlg.vue'
import {
DataZoomComponent,
GridComponent,
LegendComponent,
TooltipComponent,
} from 'echarts/components'
use([
CanvasRenderer,
TooltipComponent,
LineChart,
LegendComponent,
GridComponent,
DataZoomComponent,
])
const ZOOM_HEIGHT = 30
const ZOOM_BOTTOM = 10
const emits = defineEmits(['on-change-coefficient'])
type Legend = {
addr: string
label: string
6 months ago
unit?: string
coefficient?: string | number
operator?: string
}
const autoresize = {
throttle: 0,
}
const LineChartDlgRef = ref()
function onChangeCoefficient(data: { id: string; coefficient: string; operator: string }) {
emits('on-change-coefficient', data)
}
const onChartClick = (params: any) => {
// 判断是否点到折线
const [seriesName, id] = params?.seriesName?.split('|')
if (params.componentType === 'series' && params.seriesType === 'line' && seriesName && id) {
const find = props.legends.find(item => item.addr === id) as any
if (find) {
LineChartDlgRef.value?.open(seriesName, id, find?.coefficient, find?.operator)
}
}
}
const props = defineProps({
title: String,
smooth: Boolean, //当前是曲线图还是阶梯图
legends: {
type: Array as PropType<Legend[]>,
default: () => [],
},
axisData: {
type: Array as PropType<string[]>,
default: () => [],
},
chartDatas: {
type: Object as PropType<Map<string, any[]>>,
default: () => new Map(),
},
})
const loading = ref(true)
const loadingOpt = {
type: 'default',
text: '暂无数据',
color: '#c23531',
textColor: '#666',
maskColor: 'rgba(0, 0, 0, 0)',
showSpinner: false,
}
const chartRef = ref()
const chartOption = computed<EChartsOption>(() => {
if (props.chartDatas.size === 0) {
return {}
}
loading.value = false
const tmpSeries: SeriesOption[] = []
for (const legend of props.legends.filter(item => item.addr !== 'ts')) {
const entry = props.chartDatas.get(legend.addr) ?? []
const lineData: SeriesOption = {
name: `${legend.label}|${legend.addr}`,
type: 'line',
6 months ago
id: legend.addr,
// symbol: "none",
connectNulls: true,
triggerLineEvent: true,
data: entry.map(([ts, val]) => {
const coef = legend?.coefficient ? new Big(legend.coefficient) : new Big(1)
const operator = legend?.operator || '*'
let valBig = new Big(val ?? 0)
if (operator === '/') {
valBig = valBig.div(coef)
} else {
valBig = valBig.times(coef)
}
return [ts, valBig.toNumber()]
}),
sampling: 'lttb',
animation: false,
}
if (props.smooth) {
lineData.smooth = true
} else {
lineData.step = 'middle'
}
tmpSeries.push(lineData)
}
const dataLength = tmpSeries.length * tmpSeries[0]?.data?.length || 0;
const visibleCount = Math.min(tmpSeries.length * 1000, dataLength);
const startPercent = dataLength === 0 ? 0 : ((dataLength - visibleCount) / dataLength) * 100;
const endPercent = 100;
const maxSpanPercent = dataLength === 0 ? 100 : (visibleCount / dataLength) * 100;
const option: EChartsOption = {
grid: {
left: 60,
right: 198,
top: '5%',
bottom: `25%`,
},
tooltip: {
trigger: 'axis',
confine: true,
appendToBody: true,
formatter: function (params: any) {
// 时间格式化
const date = new Date(params[0].value[0])
const timeStr = dayjs(params[0].value[0]).format('YYYY-MM-DD HH:mm:ss.SSS')
let relVal = timeStr
for (let i = 0; i < params.length; i++) {
const data = props.legends.find(item => item.addr === params[i].seriesId)
const conefficients = data?.coefficient ?? undefined
const unit = data?.unit || ''
relVal += `<div style="display: flex; justify-content: space-between; gap: 30px;">
<div>${params[i].marker}${params[i].seriesName}:</div>
<div>${params[i].value[1]}${conefficients ? `【系数为${conefficients}(${operatorMap[data?.operator] ?? ''
})` : ''}${unit}</div>
</div>`
}
return relVal
}
},
legend: {
type: 'scroll',
orient: 'vertical',
formatter: name => {
const nameStr = name.split('|')[0]
return nameStr.length > 15 ? nameStr.substring(0, 15) + '...' : nameStr
},
tooltip: {
show: true,
formatter: function (params) {
return params.name.split('|')[0]
},
},
right: 0,
top: 20,
itemWidth: 10,
itemHeight: 10,
pageIconSize: 12,
data: props.legends.map(item => `${item.label}|${item.addr}`),
// selected: unCheckArr.value.reduce((acc, cur) => {
// acc[cur] = false
// return acc
// }, {} as Record<string, boolean>),
},
xAxis: {
type: 'time',
axisLine: {
//y轴线的颜色以及宽度
show: true,
lineStyle: {
width: 1,
type: 'solid',
},
},
axisLabel: {
show: false,
}
},
yAxis: {
type: 'value',
splitNumber: 6,
axisLine: {
//y轴线的颜色以及宽度
show: true,
lineStyle: {
width: 1,
type: 'solid',
},
},
axisLabel: {
margin: 12,
fontSize: 12,
},
splitLine: {
show: true,
lineStyle: {},
},
},
dataZoom: [
{
type: 'inside',
xAxisIndex: 0,
filterMode: 'filter',
start: startPercent,
end: endPercent,
// maxSpan: maxSpanPercent,
},
{
type: 'slider',
xAxisIndex: 0,
start: startPercent,
end: endPercent,
height: ZOOM_HEIGHT,
bottom: '20%',
borderColor: '#88abf5',
dataBackground: { lineStyle: { color: '#d2dbee' } },
selectedDataBackground: { lineStyle: { color: '#2c6cf7' } },
moveHandleStyle: { color: '#2c6cf7' },
// maxSpan: maxSpanPercent,
},
],
series: tmpSeries,
}
return option
})
const unCheckArr = ref<string[]>([])
const operatorMap = {
'*': '×',
'/': '÷',
}
function changeLegend(data: { name: string; selected: Record<string, boolean> }) {
const { name, selected } = data
if (!selected[name]) {
unCheckArr.value.push(name)
} else {
unCheckArr.value = unCheckArr.value.filter(item => item !== name)
}
}
</script>
<style scoped lang="scss">
.device-data-chart {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.chart {
width: 100%;
height: 90%;
min-height: 100px;
}
}
</style>