|
|
@@ -9,7 +9,6 @@ import echartsResize, { px2echarts } from '@/mixins/echartsResize.js';
|
|
|
|
|
|
export default {
|
|
|
name: 'TrafficTimeSpace',
|
|
|
- // 2. 注册混入,自动接管组件的图表缩放、数据重绘和销毁生命周期
|
|
|
mixins: [echartsResize],
|
|
|
props: {
|
|
|
intersections: { type: Array, required: true },
|
|
|
@@ -21,17 +20,21 @@ export default {
|
|
|
scrollSpeed: { type: Number, default: 0.5 },
|
|
|
upWaveColor: { type: String, default: 'rgba(46, 196, 182, 0.45)' },
|
|
|
downWaveColor: { type: String, default: 'rgba(104, 231, 95, 0.4)' },
|
|
|
- waveLabelColor: { type: String, default: '#e0f7fa' }
|
|
|
+ waveLabelColor: { type: String, default: '#e0f7fa' },
|
|
|
+ // 【新增】扫描线颜色配置
|
|
|
+ timeLineColor: { type: String, default: '#FF9800' }
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
scrollTimer: null,
|
|
|
- currentViewTime: 0
|
|
|
- // 删除了 chart 实例,交由 mixin 的 $_chart 管理
|
|
|
+ currentViewTime: 0,
|
|
|
+ // 【新增】控制扫描线的状态
|
|
|
+ timeLineTimer: null,
|
|
|
+ currentTimeIndicator: 0
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
|
- maxDistance() { return Math.max(...this.distances); },
|
|
|
+ maxDistance() { return Math.max(...this.distances, 100); }, // 加个兜底值防无数据报错
|
|
|
maxDataTime() {
|
|
|
let max = 0;
|
|
|
this.waveData.forEach(w => max = Math.max(max, w.xBR, w.xTR));
|
|
|
@@ -49,11 +52,14 @@ export default {
|
|
|
this.$nextTick(() => {
|
|
|
this.initChart();
|
|
|
if (this.autoScroll) this.startScroll();
|
|
|
+ // 【新增】组件挂载后启动扫描线
|
|
|
+ this.startTimeLine();
|
|
|
});
|
|
|
},
|
|
|
beforeDestroy() {
|
|
|
this.stopScroll();
|
|
|
- // 所有的 resize 和 chart.dispose 统统删掉,mixin 会帮你兜底清理!
|
|
|
+ // 【新增】销毁时清理定时器
|
|
|
+ this.stopTimeLine();
|
|
|
},
|
|
|
watch: {
|
|
|
waveData() { this.updateChart(); },
|
|
|
@@ -62,12 +68,10 @@ export default {
|
|
|
},
|
|
|
methods: {
|
|
|
initChart() {
|
|
|
- // 3. 将实例赋值给 $_chart (mixin 规定的变量)
|
|
|
this.$_chart = echarts.init(this.$refs.chartContainer);
|
|
|
this.updateChart();
|
|
|
},
|
|
|
|
|
|
- // mixin 中的 resize 会在窗口变化时自动调用这个方法
|
|
|
updateChart() {
|
|
|
if (!this.$_chart) return;
|
|
|
|
|
|
@@ -81,7 +85,6 @@ export default {
|
|
|
animation: false,
|
|
|
tooltip: { show: false },
|
|
|
grid: {
|
|
|
- // 4. 网格全部使用 px2echarts
|
|
|
left: px2echarts(80),
|
|
|
right: px2echarts(15),
|
|
|
top: px2echarts(10),
|
|
|
@@ -94,7 +97,7 @@ export default {
|
|
|
axisLabel: {
|
|
|
color: '#7b95b9',
|
|
|
formatter: '{value}s',
|
|
|
- fontSize: px2echarts(10) // 5. 坐标轴字体
|
|
|
+ fontSize: px2echarts(10)
|
|
|
},
|
|
|
splitLine: { show: true, lineStyle: { color: '#1a305d', type: 'solid' } },
|
|
|
axisLine: { lineStyle: { color: '#31548e' } }
|
|
|
@@ -108,13 +111,15 @@ export default {
|
|
|
interval: 0,
|
|
|
color: '#9cb1d4',
|
|
|
fontWeight: 'bold',
|
|
|
- fontSize: px2echarts(10), // 5. 坐标轴字体
|
|
|
+ fontSize: px2echarts(10),
|
|
|
formatter: value => distances.includes(value) ? intersections[distances.indexOf(value)] : ''
|
|
|
},
|
|
|
splitLine: { show: true, lineStyle: { color: '#1a305d' } }
|
|
|
},
|
|
|
+ // 【修改】给每个系列加上 id,新增时间线系列
|
|
|
series: [
|
|
|
{
|
|
|
+ id: 'waveSeries',
|
|
|
type: 'custom',
|
|
|
renderItem: function (params, api) { return self.renderWave(params, api); },
|
|
|
data: this.echartsWaveData,
|
|
|
@@ -122,6 +127,7 @@ export default {
|
|
|
z: 1
|
|
|
},
|
|
|
{
|
|
|
+ id: 'redSeries',
|
|
|
type: 'custom',
|
|
|
renderItem: function (params, api) { return self.renderRedBackground(params, api); },
|
|
|
data: this.echartsRedData,
|
|
|
@@ -129,21 +135,29 @@ export default {
|
|
|
z: 2
|
|
|
},
|
|
|
{
|
|
|
+ id: 'greenSeries',
|
|
|
type: 'custom',
|
|
|
renderItem: function (params, api) { return self.renderGreenLight(params, api); },
|
|
|
data: this.echartsGreenData,
|
|
|
clip: true,
|
|
|
z: 3
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'timeLineSeries', // 【新增】垂直扫描线系列
|
|
|
+ type: 'custom',
|
|
|
+ renderItem: function (params, api) { return self.renderTimeLine(params, api); },
|
|
|
+ data: [[this.currentTimeIndicator]], // 初始数据
|
|
|
+ clip: true,
|
|
|
+ z: 10 // 放在最上层
|
|
|
}
|
|
|
]
|
|
|
});
|
|
|
},
|
|
|
|
|
|
- // 6. renderItem 里的所有死像素全换成了 px2echarts
|
|
|
renderRedBackground(params, api) {
|
|
|
const y = api.value(0);
|
|
|
const startX = api.coord([0, y])[0];
|
|
|
- const endX = api.coord([this.maxDataTime, y])[0];
|
|
|
+ const endX = api.coord([this.maxDataTime || this.viewWindow, y])[0];
|
|
|
return {
|
|
|
type: 'rect',
|
|
|
shape: {
|
|
|
@@ -209,6 +223,68 @@ export default {
|
|
|
};
|
|
|
},
|
|
|
|
|
|
+ // 【新增】渲染垂直扫描线
|
|
|
+ renderTimeLine(params, api) {
|
|
|
+ const xVal = api.value(0);
|
|
|
+ const start = api.coord([xVal, 0]); // 底部坐标
|
|
|
+ const end = api.coord([xVal, this.maxDistance]); // 顶部坐标
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'line',
|
|
|
+ shape: {
|
|
|
+ x1: start[0],
|
|
|
+ y1: start[1],
|
|
|
+ x2: end[0],
|
|
|
+ y2: end[1]
|
|
|
+ },
|
|
|
+ style: {
|
|
|
+ stroke: this.timeLineColor, // 线条颜色
|
|
|
+ lineWidth: px2echarts(2), // 线条粗细
|
|
|
+ lineDash: [px2echarts(5), px2echarts(5)] // 设置为虚线,如果想实线可以删掉这行
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ // 【新增】启动扫描线动画
|
|
|
+ startTimeLine() {
|
|
|
+ this.stopTimeLine();
|
|
|
+ let lastTime = Date.now();
|
|
|
+
|
|
|
+ this.timeLineTimer = setInterval(() => {
|
|
|
+ const now = Date.now();
|
|
|
+ // 计算两次执行的时间差,转换为秒 (真实时间的 1秒 等于 X轴的 1个单位)
|
|
|
+ const delta = (now - lastTime) / 1000;
|
|
|
+ lastTime = now;
|
|
|
+
|
|
|
+ this.currentTimeIndicator += delta;
|
|
|
+
|
|
|
+ // 判定周期(当横坐标到达最大数据时间或者当前视窗的最大值时,回到原点)
|
|
|
+ const cycleLimit = this.maxDataTime > 0 ? this.maxDataTime : this.viewWindow;
|
|
|
+
|
|
|
+ if (this.currentTimeIndicator > cycleLimit) {
|
|
|
+ this.currentTimeIndicator = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.$_chart) {
|
|
|
+ // 精准更新:只传入对应 id 的系列数据,避免整个图表重绘引发卡顿
|
|
|
+ this.$_chart.setOption({
|
|
|
+ series: [{
|
|
|
+ id: 'timeLineSeries',
|
|
|
+ data: [[this.currentTimeIndicator]]
|
|
|
+ }]
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }, 16); // ~60fps 的刷新率,保证视觉极其平滑
|
|
|
+ },
|
|
|
+
|
|
|
+ // 【新增】停止扫描线动画
|
|
|
+ stopTimeLine() {
|
|
|
+ if (this.timeLineTimer) {
|
|
|
+ clearInterval(this.timeLineTimer);
|
|
|
+ this.timeLineTimer = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
startScroll() {
|
|
|
this.stopScroll();
|
|
|
this.scrollTimer = setInterval(() => {
|
|
|
@@ -217,7 +293,6 @@ export default {
|
|
|
this.currentViewTime = 0;
|
|
|
}
|
|
|
if (this.$_chart) {
|
|
|
- // 注意这里也要换成 $_chart
|
|
|
this.$_chart.setOption({
|
|
|
xAxis: { min: this.currentViewTime, max: this.currentViewTime + this.viewWindow }
|
|
|
});
|