el-table与echarts图形滚动联动
效果图
实现思路
设计图滚动条位于表格下方,且echarts滚动不易获取当前展示数据到左侧的距离
故:通过监听表格的滚动实现联动
- 为了保持echarts的横坐标和表格的列基本保持对齐,用
tdWidth
标识单列表格的宽度,showTableTd
标识展示了多少列,在mounted
计算当前容器宽度能展示多少列,保证是整数列; - echarts设置
dataZoom
,并且禁用鼠标滚动滑动的功能; - 给表格添加
scroll
监听,注意添加节流,避免出现滚动时反复计算,无法滚动和浪费资源; - 计算图表
dataZoom
的start
和end
控制图表展示的区域。
实现代码:
scrollTableLine.vue
<template>
<div>
<div class="pdl-42 ">
<lineChart ref="charts" class="mgt-12" :chartData="chartData" height="90px" :optionOpt="optionOpt" />
</div>
<data-table ref="tableRef" :pagination="false" size="mini" class="mgt-12 data-table pdb-0" :head="tableHead"
:data="tableData">
</data-table>
</div>
</template>
<script>
import { throttle } from '@/utils/index.js'
export default {
name: "scrollTableLine",
components: {
lineChart: () => import("@/components/Chart/lineChart.vue"),
},
props: {
data: {
type: Array,
default: () => [],
},
},
data() {
return {
chartData: [
//...设置数据
],
tableHead: [
{
prop: "type",
label: "",
width: 70,
fixed: "left",
},
],
tableData: [],
showTableTd: 0,
tdWidth: 0,
};
},
beforeDestroy() {
this.removeScrollListener();
},
mounted() {
let width = this.$refs.tableRef.$el.clientWidth;
//初始按照75的宽度计算
this.showTableTd = Math.floor((width - 70) / 65);
//计算每一列的宽度
this.tdWidth = Math.floor((width - 70) / this.showTableTd);
let data = {}
//制造el-table假数据
for (let i = 0; i < 30; i++) {
data[`day${i}`] = 10
this.tableHead.push({
prop: `day${i}`,
label: `01-${i + 1 < 10 ? "0" + (i + 1) : i + 1}`,
width: this.tdWidth,
align: "center",
});
}
//表格数据
this.tableData = [
{
type: "XX数",
...data
},
{
type: "XX数",
...data
}
]
this.addScrollListener();
},
methods: {
//给echarts设置配置项及dataZoom
optionOpt(option) {
option.color = ["#1484FA"];
option.grid.top = 8;
option.legend.show = false;
option.yAxis[0].axisLabel.formatter = (value) => {
return value + "%";
};
option.tooltip.formatter = (params) => {
let text = `<div class="lh-18 color-333 fs-12"><p>${params[0].axisValue}</p> `;
params.forEach((item) => {
text += ` <p ><span class="fw-4 color-999">${item.seriesName}:</span> <span style="color:#333;"></span>${item.value}%</p> `;
});
text += "</div>";
return text;
};
option.xAxis[0].axisLabel.interval = 0;
option.dataZoom = [
{
type: "slider",
show: false,
zoomLock: false,
right: "2%",
start: 0,
bottom: 2,
end: 40,
showDetail: false,
zoomOnMouseWheel: false,
backgroundColor: "transparent", //两边未选中的滑动条区域的颜色
borderColor: "transparent",
filterMode: "empty",
}
];
},
//添加滚动监听
addScrollListener() {
const tableBodyWrapper = this.$refs.tableRef.$el.querySelector(
".el-table__body-wrapper"
);
//展示个数
if (tableBodyWrapper) {
let handleScroll = throttle(
this.handleScroll,
200
);
tableBodyWrapper.addEventListener("scroll", handleScroll);
}
this.$nextTick(() => {
setTimeout(() => {
this.handleScroll();
}, 600);
});
},
//移出监听
removeScrollListener() {
const tableBodyWrapper = this.$refs.tableRef.$el.querySelector(
".el-table__body-wrapper"
);
if (tableBodyWrapper) {
tableBodyWrapper.removeEventListener("scroll", this.handleScroll);
}
},
//表格滚动的时候计算滚动
handleScroll(event) {
const scrollLeft = event?.target?.scrollLeft || 0;
let leftNum = Math.ceil(scrollLeft / this.tdWidth);
if (event) event.target.scrollLeft = leftNum * this.tdWidth;
this.$refs.charts.$refs.chart.setOption({
dataZoom: [
{
start: (leftNum / this.chartData.length) * 100,
end:
((leftNum + this.showTableTd) / this.chartData.length) * 100 -
(scrollLeft == 0 ? 3.3 : 0),
},
],
});
},
},
};
</script>
<style lang="scss" scoped>
::v-deep .data-table {
background: #f5f9ff;
.el-table {
background: #f5f9ff;
}
.el-table__cell {
padding: 2px 0;
}
tr {
background: #fff;
}
.el-table__body-wrapper::-webkit-scrollbar {
width: 4px;
height: 4px;
background-color: #fff;
}
.el-table__body-wrapper::-webkit-scrollbar-track {
box-shadow: 0px 1px 3px #fff inset;
border-radius: 10px;
background-color: #fff;
}
.el-table__body-wrapper::-webkit-scrollbar-thumb {
box-shadow: 0px 1px 3px #c3cbd6 inset;
border-radius: 10px;
background-color: #c3cbd6;
}
}
.pdl-42 {
padding-left: 40px;
}
</style>
lineChart.vue
<template>
<vue-chart v-bind="$attrs" v-on="$listeners" ref="chart"
:style="{ height: height, width: width }" :option="option" />
</template>
<script>
import minix from './minix'
export default {
name: 'barChart',
mixins: [minix],
props: {
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '143px'
},
chartData: {
type: Array,
default: () => [
{ name: '5月', value: 80, value2: 70, value3: 20 },
{ name: '6月', value: 50, value2: 40, value3: 85 },
{ name: '7月', value: 30, value2: 10, value3: 70 },
{ name: '8月', value: 60, value2: 70, value3: 30 },
{ name: '9月', value: 50, value2: 40, value3: 80 },
{ name: '10月', value: 20, value2: 10, value3: 60 },
],
},
optionOpt: {
type: Function,
default: () => () => { }
},
},
watch: {
chartData: {
handler(val) {
if (val) {
let Xdata = []
let Ydata = []
val.forEach(item => {
Xdata.push(item.name)
Ydata.push(item?.value ?? 0)
})
this.option.xAxis[0].data = Xdata
this.option.series[0].data = Ydata
this.optionOpt(this.option, this.chartData)
}
},
immediate: true,
deep: true
}
},
data() {
return {
option: {
color: ['rgba(67, 207, 124, 1)', 'rgba(253, 196, 67, 1)', 'rgba(227, 69, 69, 1)'],
grid: {
left: "0%",
right: "1",
bottom: "0%",
top: "32",
containLabel: true,
},
tooltip: {
trigger: 'axis',
confine: true,
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderColor: 'rgba(235, 240, 250, 1)',
borderWidth: 1,
extraCssText: 'box-shadow: 0px 2px 8px rgba(205, 207, 209, 1);line-height:18px;padding:9px 16px 15px;backdrop-filter: blur(5px);',
axisPointer: {
type: "shadow",
shadowStyle: {
color: 'rgba(20, 132, 250, 0)',
width: 40,
}
},
textStyle: {
color: '#333333',
fontSize: 12
},
formatter: (params) => {
let text = `<div class="lh-18 color-333 fs-12">
<p>${params[0].axisValue}</p> `
params.forEach(item => {
text += ` <p ><span class="fw-4 color-999">${item.seriesName}:</span> <span style="color:#333;"></span>${item.value}</p> `
})
text += '</div>'
return text
},
},
legend: {
itemGap: 12,
itemWidth: 11, // 设置宽度
itemHeight: 7,
left: 0,
top: '0',
right: '30',
textStyle: {
color: '#666666',
fontSize: 12
},
},
xAxis: [{
data: [],
axisPointer: {
"type": "shadow"
},
axisLine: {
"lineStyle": {
"color": "rgba(227, 227, 227, 1)"
}
},
splitLine: {
show: true,
lineStyle: {
color: 'rgba(227, 227, 227, 1)',
width: 1,
type: 'dashed'
}
},
axisTick: {
show: false
},
axisLabel: {
show: true,
textStyle: {
color: "#999999",
fontSize: 12
}
},
}],
yAxis: [{
type: "value",
splitNumber: 2,
splitLine: {
show: true,
lineStyle: {
color: 'rgba(227, 227, 227, 1)',
width: 1,
type: 'dashed'
}
},
name: "",
nameTextStyle: {
color: "#999",
align: 'left'
},
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(227, 227, 227, 1)'
}
},
axisLabel: {
show: true,
textStyle: {
color: "rgba(153, 153, 153, 1)",
fontSize: 12
}
},
},
//右边的虚线
{
type: "value",
splitNumber: 5,
max: 100,
min: 0,
yIndex: 1,
nameGap: 2,
position: 'right',
splitLine: {
show: false,
},
name: "%",
nameTextStyle: {
color: "#999",
align: 'left',
padding: [0, 0, 10, 10]
},
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(227, 227, 227, 0)',
type: 'dashed',
zIndex: 10
}
},
axisLabel: {
show: false,
textStyle: {
color: "rgba(153, 153, 153, 1)",
fontSize: 0,
}
},
}],
series: [
{
name: "日增变化趋势",
type: "line",
smooth: false,
showAllSymbol: true,
symbol: "emptyCircle",
symbolSize: 6,
data: [],
},
]
}
}
},
methods: {
}
}
</script>
<style scoped></style>