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}
/>
);
};
效果