需求背景
需要实线一个同x轴,且图表的的多图表联动效果
解决效果
视频效果
ISQQW代码地址
index.vue
<!--/**
* @author: liuk
* @date: 2023/11/13
* @describe: 共x轴,半虚线天气图表
*/-->
<template>
<div ref="chatDom" class="HeatPredictionChart"></div>
</template>
<script lang="ts" setup>
import {ref, onMounted, watch, nextTick} from "vue"
import * as echarts from 'echarts'
import moment from "moment";
// Props
const props = defineProps(['data', 'typeArr'])
let myChart = null // Vue3 使用 proxy 对象代理,而 echarts 则使用了大量的全等(===), 对比失败从而导致了bug。
const chatDom = ref(null)
watch(() => props.typeArr, () => {
const option = myChart.getOption()
myChart.clear()
myChart.setOption(renderFn(option, props.data, props.typeArr))
}, {deep: true})
watch(() => props.data, () => {
nextTick(() => {
const option = myChart.getOption()
myChart.clear()
myChart.setOption(renderFn(option, props.data, props.typeArr))
})
}, {immediate: true})
const renderFn = (option, data, types) => {
types = types.map(id => selectOptions.find(item => item.id === id))
const day = data.heat_power_forecast_day[0]
const thatDayTime = +new Date(day) //指定天8点,秒级时间戳
const curDayTime = new Date(new Date().toLocaleDateString()).setHours(8)//今天8点,秒级时间戳
option.title[0].text = day?.slice(-5)
option.yAxis[0].name = ''
option.yAxis[1].name = ''
option.series[2].name = ''
option.series[2].data = []
option.series[3].name = ''
option.series[3].data = []
option.series[4].data = []
option.series[5].data = []
if (moment(new Date()).format('YYYY-MM-DD') === day) { // 如果是当天
const curRemainHourNum = new Date().getHours() <= 7 ? 8 - new Date().getHours() - 1 : 24 - new Date().getHours() + 8;
option.series[4].data = data.t3_forecast_hours.slice(0, 24 - curRemainHourNum + 1).concat(data.t3_forecast_hours.slice(24 - curRemainHourNum - 1).fill('-'))
option.series[5].data = data.t3_forecast_hours.slice(0, 24 - curRemainHourNum).fill('-').concat(data.t3_forecast_hours.slice(24 - curRemainHourNum))
option.series[5].lineStyle.type = 'dashed'
new Array(2).fill('').forEach((_, i) => {
if (!types[i]) {
option.series[i].data = []
return
}
const arr = data[types[i].value]
option.series[i].data = arr.slice(0, 24 - curRemainHourNum + 1).concat(arr.slice(24 - curRemainHourNum - 1).fill('-'))
option.series[i].name = types[i].label
option.series[i].lineStyle.type = 'solid'
option.series[i].color = types[i].color
option.series[i + 2].data = arr.slice(0, 24 - curRemainHourNum).fill('-').concat(arr.slice(24 - curRemainHourNum))
option.series[i + 2].name = types[i].label
option.series[i + 2].lineStyle.type = 'dashed'
option.series[i + 2].color = types[i].color
option.yAxis[i].name = types[i].label?.slice(-2) + ': ' + types[i].unit
})
} else {
option.series[4].data = data.t3_forecast_hours
new Array(2).fill('').forEach((_, i) => {
if (!types[i]) {
option.series[i].data = []
return
}
option.series[i].data = data[types[i].value]
option.series[i].name = types[i].label
option.series[i].color = types[i].color
option.series[i].lineStyle.type = thatDayTime - 24 * 60 * 60 * 1e3 >= curDayTime ? 'dashed' : 'solid'
})
}
return option
}
onMounted(() => {
drawChart()
window.addEventListener('resize', () => {
const option = myChart.getOption()
myChart.clear()
myChart.setOption(renderFn(option, props.data, props.typeArr))
}, {passive: true});
})
const drawChart = () => {
let chartDom = chatDom.value
if (chartDom == '-') {
return
}
echarts.dispose(chartDom)
myChart = echarts.init(chartDom)
const option = {
title: {
x: 'left',
padding: [28, 0, 0, 10],
text: '11-09',
textStyle: {//设置主标题的文字风格
color: "rgba(165,166,166,1)", //字体颜色
fontSize: 13, //文字大小
opacity: 0.9
},
},
grid: [
{
top: '17%',
left: 90,
right: 90,
height: '36%'
},
{
top: '60%',
left: 90,
right: 90,
height: '36%'
}
],
axisPointer: {
type: 'shadow',
link: {xAxisIndex: 'all'},
},
tooltip: {
trigger: 'axis',
axisPointer: { // 设置指示线
type: 'line', // 默认为直线,可选为:'line' | 'shadow'
lineStyle: {
color: '#8C8C8C',
type: [4.5, 5], //设置折线类型
dashOffset: 5,
width: 2,
}
},
transitionDuration: 0,
confine: true,
borderRadius: 2,
borderWidth: 0,
backgroundColor: 'rgba(149,149,149,0.15)',
backdropFilter: 'brightness(80%) blur(4px)',
borderColor: 'rgba(149,149,149,0.8)',
textStyle: {fontSize: 14, color: '#fff'},
formatter: function (param) {
let data = param.filter(item => item.data !== '-' && item.name)
data = data.filter((item, i) => data.findIndex(x => x.seriesName === item.seriesName) === i)
const dataIndex = param[0].dataIndex
return `
<div class="detailChat-popup">
<p class="top">
<span>${props.data.heat_power_forecast_day[dataIndex]}</span>
<span>${data[0].name || '暂无'} </span>
</p>
${
data.map(item => {
let unit
switch (true) {
case ['实际热耗', '预测热需'].includes(item.seriesName):
unit = 'GJ'
break
case ['预测供水温度', '预测回水温度', '室外温度'].includes(item.seriesName):
unit = '℃'
break
case ['预测流量'].includes(item.seriesName):
unit = 'T'
break
}
return `
<p class="item">
<i class="icon" style="background-color:${item.color}"></i>
<span class="name">${item.seriesName}</span>
<span>${item.data}</span>
<span>${unit}</span>
</p>`
}).join("")
}
</div>
`
}
},
xAxis: [
{
data: ['08:00', '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', '18:00', '19:00', '20:00', '21:00', '22:00', '23:00', '00:00', '01:00', '02:00', '03:00', '04:00', '05:00', '06:00', '07:00'],
boundaryGap: false,
min: 0,
axisLine: {
show: true,
},
position: 'top',
axisTick: {
show: false,
},
axisLabel: {
margin: 80,
show: true,
textStyle: {
color: 'rgba(165,166,166,1)',
fontSize: '12',
},
},
},
{
gridIndex: 1,
data: ['08:00', '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', '18:00', '19:00', '20:00', '21:00', '22:00', '23:00', '00:00', '01:00', '02:00', '03:00', '04:00', '05:00', '06:00', '07:00'],
boundaryGap: false,
min: 0,
axisLabel: {show: false},
axisLine: {
show: false,
},
position: 'top',
axisTick: {
show: false,
}
}
],
yAxis: [
{
gridIndex: 0,
name: '热耗:GJ/h',
nameGap: '25',
offset: 20,
nameTextStyle: {
padding: [0, 55, 5, 0],
color: '#fff',
fontSize: '14',
opacity: 0.7,
align: 'center'
},
nameLocation: 'end',
position: 'left',
axisTick: {show: false},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: 'rgba(52,52,52,1)'
}
},
axisLine: {show: false,},
axisLabel: {
show: true,
showMinLabel: false,
textStyle: {
color: 'rgba(165,166,166,1)',
fontSize: '14',
}
},
},
{
gridIndex: 0,
nameGap: '25',
name: '流量:T/h',
offset: 20,
nameTextStyle: {
padding: [0, 55, 5, 0],
color: '#fff',
fontSize: '14',
opacity: 0.7,
align: 'center'
},
nameLocation: 'end',
position: 'right',
type: 'value',
axisTick: {show: false},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: 'rgba(52,52,52,1)'
}
},
axisLine: {show: false,},
axisLabel: {
show: true,
showMinLabel: false,
textStyle: {
color: 'rgba(165,166,166,1)',
fontSize: '14',
}
},
},
{
gridIndex: 1,
name: '室外温度/°C',
nameGap: '25',
offset: 20,
nameTextStyle: {
padding: [0, 55, 5, 0],
color: '#fff',
fontSize: '14',
opacity: 0.7,
align: 'center'
},
nameLocation: 'end',
position: 'left',
axisTick: {show: false},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: 'rgba(52,52,52,1)'
}
},
axisLine: {show: false,},
axisLabel: {
show: true,
showMinLabel: true,
textStyle: {
color: 'rgba(165,166,166,1)',
fontSize: '14',
}
},
},
],
series: [
{
name: '实际热耗',
type: 'line',
showSymbol: false,
data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-'],
xAxisIndex: 0,
yAxisIndex: 0,
lineStyle: {
normal: {
type: 'solid'
}
},
color: '#ffffff'
},
{
name: '实际热耗',
type: 'line',
showSymbol: false,
data: ['-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24],
xAxisIndex: 0,
yAxisIndex: 1,
lineStyle: {
normal: {
type: 'dashed'
}
},
color: '#ffffff'
},
{
name: '实际热耗',
type: 'line',
showSymbol: false,
data: ["-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"],
xAxisIndex: 0,
yAxisIndex: 0,
lineStyle: {
normal: {
type: 'dashed'
}
},
color: '#ffffff'
},
{
name: '实际热耗',
type: 'line',
showSymbol: false,
data: ["-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"],
xAxisIndex: 0,
yAxisIndex: 1,
lineStyle: {
normal: {
type: 'dashed'
}
},
color: '#ffffff'
},
{
name: '室外温度',
type: 'line',
showSymbol: false,
data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-'],
xAxisIndex: 1,
yAxisIndex: 2,
color: 'rgba(0,207,163,1)',
},
{
name: '室外温度',
type: 'line',
showSymbol: false,
data: ["-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"],
xAxisIndex: 1,
yAxisIndex: 2,
color: 'rgba(0,207,163,1)',
},
]
}
option && myChart.setOption(option)
}
const selectOptions = [
{
id: '1',
value: 'heat_power_forecast_loss',
label: '实际热耗',
color: '#ffffff',
unit: 'GJ/h'
},
{
id: '2',
value: 'heat_power_forecast_hours',
label: '预测热需',
color: 'rgba(255,191,0,1)',
unit: 'GJ/h'
},
{
id: '3',
value: 'tt011_forecast_hours',
label: '预测供水温度',
color: 'rgba(255, 64, 25,1)',
unit: '°C'
},
{
id: '4',
value: 'ft021_forecast_hours',
label: '预测流量',
color: 'rgba(184,102,238,1)',
unit: 'T/h'
},
{
id: '5',
value: 'tt011_forecast_hours',
label: '预测回水温度',
color: 'rgba(0, 132, 255,1)',
unit: '°C'
},
]
</script>
<style lang="scss" scoped>
.HeatPredictionChart {
width: 100%;
height: 100%;
}
</style>
<style lang="scss">
.detailChat-popup {
overflow: hidden;
.top {
margin-bottom: 5px;
}
.item {
display: flex;
align-items: center;
margin: 10px 0;
&:last-child {
margin-bottom: 0;
}
.icon {
display: inline-block;
width: 12px;
height: 2px;
margin-right: 10px;
border-radius: 2px;
}
.name {
margin-right: 40px;
}
}
}
</style>