需求:有三个series,分别代表实际值、上限值、下限值,x轴坐标是日期,当某个日期的实际值大于上限值时,实际值的该段折线显示红色,否则显示蓝色
解决方案:使用 echarts.graphic.LinearGradient 手动构建分段渐变,让线段跟随对应数据点的颜色显示
核心思路:为每一段线段(两个数据点之间)单独构建「单色渐变」(起点和终点颜色一致,视觉上就是纯色线段),通过多段渐变拼接实现分段染色效果
这样写开启legend时图例依旧可以控制该条折线的显示/隐藏或点击事件。并且!!!tooltip里面的标记色也可以自动根据当前分段颜色调整
typescript
import * as echarts from 'echarts'; // 在echarts在线编辑器中调试时把这行去掉即可
let xAxisData = ['1日', '2日', '3日', '4日', '5日', '6日'];
let upperLimit = [10,20,30,40,50,60]; // 上限值
let calcValue = [20,3,40,5,60,70]; // 实际值
let lowLimit = [5, 70, 23, 45, 56, 89]; // 下限值
const realValueColors = [];
for (let i = 0; i < calcValue.length; i++) {
const realValue = Number(calcValue[i]);
const upperValue = Number(upperLimit[i]);
if (isNaN(realValue) || isNaN(upperValue)) {
realValueColors.push('#3F8CFF');
continue;
}
if (realValue > upperValue) {
realValueColors.push('#FF0000');
} else {
realValueColors.push('#3F8CFF');
}
}
// 核心修复:构建分段渐变颜色配置
function getSegmentLineColor() {
const colorStops = [];
const dataLen = calcValue.length;
// 遍历每个数据点,为相邻两个点之间的线段配置单色渐变
for (let i = 0; i < dataLen; i++) {
// 计算当前数据点对应的渐变位置(0~1之间)
const position = i / (dataLen - 1);
// 每个线段对应两个渐变节点(起点和终点颜色一致,形成纯色线段)
colorStops.push({
offset: position,
color: realValueColors[i] || '#3F8CFF'
});
// 避免重复添加最后一个节点
if (i < dataLen - 1) {
colorStops.push({
offset: (i + 1) / (dataLen - 1),
color: realValueColors[i] || '#3F8CFF'
});
}
}
// 返回线性渐变配置,作为lineStyle的color值
return new echarts.graphic.LinearGradient(
0, 0, 1, 0, // 水平渐变(适配X轴方向的折线),参数:x0, y0, x1, y1
colorStops, // 分段渐变颜色节点
false
);
}
const getMaxYValue = (arr) => {
return arr.length>0 ? String(Math.max(...arr)).length : 0;
};
option = {
color: ['#FFC402', '#0FD150', '#3F8CFF'],
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderColor: '#ddd',
borderWidth: 1,
textStyle: { color: '#333' },
formatter: (params) => {
let res = `<div style="font-weight: bold">${params[0].name}</div>`;
params.forEach((item) => {
res += `<div style="display: flex; gap: 8px; align-items: center;">
<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: ${item.color};"></span>
<span>${item.seriesName}: ${item.value || '-'}</span>
</div>`;
});
return res;
},
},
legend: {
icon: 'rect',
itemWidth: 9,
itemHeight: 1,
itemGap: 10,
right: 0,
textStyle: {
color: '#666666',
fontFamily: 'Source Han Sans CN, Source Han Sans CN',
fontWeight: 400,
fontSize: '12px',
},
},
grid: {
top: 40,
right: 0,
bottom: 60,
left: 30,
left: String(getMaxYValue([...upperLimit, ...lowLimit, ...calcValue])).length * 10,
containLabel: true,
},
xAxis: {
type: 'category',
data: xAxisData,
axisLine: {
lineStyle: {
color: '#BFBFBF'
}
},
axisTick: { show: false },
axisLabel: {
fontFamily: 'Source Han Sans CN, Source Han Sans CN',
fontWeight: 400,
fontSize: '10px',
color: '#8C8C8C',
},
},
yAxis: {
type: 'value',
axisLine: { show: false },
axisTick: { show: false },
splitLine: {
lineStyle: {
color: '#BFBFBF',
type: 'dashed',
}
},
axisLabel: {
fontFamily: 'Source Han Sans CN, Source Han Sans CN',
fontWeight: 400,
fontSize: '11px',
color: '#999999',
},
},
series: [
{
name: '上限值',
type: 'line',
data: upperLimit,
symbol: 'circle',
smooth: true,
symbolSize: 6,
lineStyle: {
width: 2,
},
},
{
name: '下限值',
type: 'line',
data: lowLimit,
symbol: 'circle',
smooth: true,
symbolSize: 6,
lineStyle: {
width: 2,
},
},
{
name: '实际值',
type: 'line',
data: calcValue,
symbol: 'circle',
smooth: true,
symbolSize: 6,
lineStyle: {
width: 2,
color: getSegmentLineColor()
},
itemStyle: {
color: (params) => {
return realValueColors[params.dataIndex] || '#3F8CFF';
}
}
}
],
}