echarts 兩個曲線之間填充並且不遮擋的辦法

ifnk發表於2024-04-16

echarts 兩個曲線之間填充 可以用兩條曲線 ,第一條填充白色 ,然後 第2條填充想要的顏色 ,如下面的程式碼

option = {
    title: {
        text: '堆疊區域圖'
    },
    tooltip : {
        trigger: 'axis'
    },
    legend: {
        data:['最小值','最大值']
    },
    toolbox: {
        feature: {
            saveAsImage: {}
        }
    },
    grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
    },
    xAxis : [
        {
            type : 'category',
            boundaryGap : false,
            data : ['週一','週二','週三','週四','週五','週六','週日']
        }
    ],
    yAxis : [
        {
            type : 'value'
        }
    ],
    series : [
        {
            name:'最小值',
            type:'line',
          //  stack: '總量',
            areaStyle: {
                color:'#8DA2E4',
                opacity:1,
                origin:"start"
            },
            data:[320, 332, 301, -40, -30, 330, 320]
        },
        {
            name:'最大值',
            type:'line',
         //   stack: '總量',
            label: {
                normal: {
                    show: true
                }
            },
            areaStyle: {
                color:'#ffffff',
                opacity:1,
                origin:"start"
            },
            data:[10, 30, 40, -60, -50, 40, 50]
        }
    ]
};

這樣有個弊端,當你 想要繪製markarea 區域的時候 ,由於下面的 曲線填充是白色的會被遮住

所以想了個辦法 ,分兩片區域繪製 分上半部分和下半部分 ,程式碼如下

import { useMemo, useState } from "react";
import ReactECharts from "echarts-for-react";
import dayjs from "dayjs";

const areaStyle = {
  opacity: 0.8,
  color: {
    type: "linear",
    x: 0,
    y: 0,
    x2: 0,
    y2: 1,
    colorStops: [
      {
        offset: 0,
        color: "rgb(0, 221, 255)",
      },
      {
        offset: 1,
        color: "rgb(77, 119, 255)",
      },
    ],
  },
};
const getCommonSeries = (name, data, markAreaData, areaStyle = undefined) => {
  return {
    name,
    areaStyle,
    data,
    type: "line",
    smooth: true,
    lineStyle: { width: 0 },
    showSymbol: false,
    markArea: {
      itemStyle: {
        color: "rgba(255, 0, 0, 0.2)",
      },
      data: markAreaData,
    },
  };
};

export const AlarmCurve = ({
  pointCurveData = [], // 點曲線資料
  alarmData = [], // 報警資料
  max = 0, // 最大值
  min = 0, // 最小值
  predictData = [], // 點預測資料
}) => {
  const pointCurveMaxData = useMemo(() => {
    return pointCurveData.map((item) => [item[0], item[1] + max]);
  }, [pointCurveData, max]);
  const pointCurveMinData = useMemo(() => {
    return pointCurveData.map((item) => [item[0], item[1] - min]);
  }, [pointCurveData, min]);
  const markAreaData = useMemo(() => {
    // 獲取 alarmData 中連續的1的區間,記錄的是 的開始和結束item[0]
    let result = [];
    let start = null;
    for (let i = 0; i < alarmData.length; i++) {
      if (alarmData[i][1] === 1) {
        if (start === null) {
          start = alarmData[i][0];
        }
        if (i === alarmData.length - 1 || alarmData[i + 1][1] === 0) {
          result.push([
            { xAxis: dayjs(start).format("MM-DD HH:mm:ss") },
            { xAxis: dayjs(alarmData[i][0]).format("MM-DD HH:mm:ss") },
          ]);
          start = null;
        }
      }
    }
    console.log("result", result);

    return result;
  }, [alarmData]);

  const opt = useMemo(() => {
    if (pointCurveData.length === 0) {
      return {};
    }
    // line opt
    return {
      title: {
        text: "報警曲線",
        // align center
        left: "center",
      },
      tooltip: {
        trigger: "axis",
        axisPointer: {
          type: "cross",
          label: {
            backgroundColor: "#6a7985",
          },
        },
      },
      grid: {
        top: 30,
        left: "3%",
        right: "4%",
        bottom: "3%",
        containLabel: true,
      },
      xAxis: [
        {
          type: "category",
          boundaryGap: false,
          data: pointCurveData.map((item) =>
            dayjs(item[0]).format("MM-DD HH:mm:ss"),
          ),
        },
      ],
      yAxis: {
        type: "value",
      },
      series: [
        {
          name: "Line 1-max",
          type: "line",
          stack: "max",
          smooth: true,
          lineStyle: {
            width: 0,
          },
          showSymbol: false,
          data: pointCurveData.map((item) => item[1]),
        },
        {
          name: "Line 1-base",
          type: "line",
          stack: "max",
          smooth: true,
          lineStyle: {
            width: 0,
          },
          showSymbol: false,
          emphasis: {
            focus: "series",
          },
          areaStyle: {
            opacity: 0.3,
            color: "rgb(13,185,88)",
          },
          data: pointCurveData.map((item) => max),
        },
        {
          ...getCommonSeries(
            "實時值",
            pointCurveData.map((item) => item[1]),
            undefined,
          ),
          lineStyle: {
            width: 2,
          },
        },

        {
          name: "Line 1-min",
          type: "line",
          stack: "min",
          smooth: true,
          lineStyle: {
            width: 0,
          },
          showSymbol: false,
          emphasis: {
            focus: "series",
          },
          data: pointCurveData.map((item) => item[1] - min),
        },
        {
          name: "Line 3",
          type: "line",
          stack: "min",
          smooth: true,
          lineStyle: {
            width: 0,
          },
          showSymbol: false,
          areaStyle: {
            opacity: 0.3,
            color: "rgb(13,185,88)",
          },
          data: pointCurveData.map((item) => min),
        },
        {
          ...getCommonSeries(
            "預測值",
            predictData.map((item) => item[1]),
            markAreaData,
          ),
          lineStyle: {
            width: 2,
          },
        },
      ],
    };
  }, [
    pointCurveData,
    alarmData,
    max,
    min,
    predictData,
    pointCurveMaxData,
    pointCurveMinData,
  ]);

  return <ReactECharts option={opt} />;
};

模擬資料呼叫

import dayjs from "dayjs";
import { AlarmCurve } from "./alarm-curve";

export const movingAverage = (data, numberOfPricePoints) => {
  return data.map((row, index, total) => {
    const start = Math.max(0, index - numberOfPricePoints);
    const subset = total.map((x) => x[1]).slice(start, index + 1);
    const sum = subset.reduce((a, b) => a + b, 0);
    return [row[0], sum / subset.length];
  });
};

export const FuXianAlarmCurve = () => {
  const now = dayjs();
  const max = 20;
  const min = 10;
  // 模擬一個1000個點的資料1秒
  const pointCurveData = movingAverage(
    Array.from({ length: 1000 }, (_, i) => [
      now.add(i, "second").valueOf(),
      Math.random() * 100,
    ]),
    20,
  );
  // 模擬一個1000個點的資料1秒
  const predictData = movingAverage(
    Array.from({ length: 1000 }, (_, i) => [
      now.add(i, "second").valueOf(),
      Math.random() * 100,
    ]),
    100,
  );
  // 遍歷pointCurveData和 predictData,如果predictData 對應的值在min和max之間, 則返回0 否則返回1
  const alarmData = pointCurveData.map((item, index) => {
    const predictItem = predictData[index];
    if (predictItem[1] > item[1] + max || predictItem[1] < item[1] - min) {
      return [item[0], 1];
    }
    return [item[0], 0];
  });

  return (
    <AlarmCurve
      pointCurveData={pointCurveData}
      predictData={predictData}
      alarmData={alarmData}
      max={max}
      min={min}
    />
  );
};

效果

相關文章