【echarts】折线图颜色分段设置不同颜色

需求:有三个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';
        }
      }
    }
  ],
}
相关推荐
阿蒙Amon3 小时前
TypeScript学习-第7章:泛型(Generic)
javascript·学习·typescript
睡美人的小仙女1273 小时前
Threejs加载环境贴图报错Bad File Format: bad initial token
开发语言·javascript·redis
fanruitian3 小时前
uniapp android开发 测试板本与发行版本
前端·javascript·uni-app
rayufo3 小时前
【工具】列出指定文件夹下所有的目录和文件
开发语言·前端·python
RANCE_atttackkk3 小时前
[Java]实现使用邮箱找回密码的功能
java·开发语言·前端·spring boot·intellij-idea·idea
摘星编程4 小时前
React Native + OpenHarmony:Timeline垂直时间轴
javascript·react native·react.js
2501_944525545 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 支出分析页面
android·开发语言·前端·javascript·flutter
jin1233225 小时前
React Native鸿蒙跨平台完成剧本杀组队详情页面,可以复用桌游、团建、赛事等各类组队详情页开发
javascript·react native·react.js·ecmascript·harmonyos
李白你好5 小时前
Burp Suite插件用于自动检测Web应用程序中的未授权访问漏洞
前端
经年未远6 小时前
vue3中实现耳机和扬声器切换方案
javascript·学习·vue