Search code examples
echarts

Apply colour to filled area in line chart matching line colour with visualMap


I came across an almost complete solution for my needs in https://github.com/apache/echarts/issues/8034, however, the fill colour isn't being applied correctly, as seen in: enter image description here and

enter image description here

Compared to what the issue shows should be something akin to: enter image description here

I'm not entirely sure if the attached image actually corresponds to the code in the issue though... but the fill apparently should match the line colour, but I'm not seeing this in my testing. I did have to change dimension in visualMap from 0 to 1 to get the line segments to change colour. Setting to 0, I get a blue fill + blue line, setting to 2, I get a yellow fill + yellow line, setting to 1, I get a blue fill + changing line colours...

Current echarts setup (data is x-axis: 1-N (simple index value), y-axis: values 0 <=> 1000):

async function createLineChartNew(element) {
    echarts.registerTheme("sauce", theme.getTheme("dynamic"));
    addEventListener("resize", resizeCharts);
    
    const dataPoints = defaultLineChartLen;
    const fields = [
        {
        id: "power",
        name: "Power",
        color: "rgb(255,255,255)",
        domain: [0, 1000],
        rangeAlpha: [0.4, 1],
        points: [],
        get: (x) => x.state.power || 0,
        fmt: (x) => H.power(x, { seperator: " ", suffix: true }),
        },
    ];
    var options = {
        title: {
        show: false,
        },
        tooltip: {
            className: "ec-tooltip",
            trigger: "axis",
        axisPointer: {
            type: "cross",
            lineStyle: {
            type: "dashed",
            },
            label: {
            backgroundColor: "#38b5ca",
            },
        },
        },
        toolbox: {
        show: false,
        },
        xAxis: {
        boundaryGap: false,
        splitLine: {
            lineStyle: {
            color: "#ededed",
            },
        },
        axisLine: {
            lineStyle: {
            color: "#ededed",
            },
        },
        axisPointer: {
            snap: true,
        },
        axisLabel: {
            color: "#e0e0e0",
        },
        data: Array.from(new Array(dataPoints)).map((x, i) => i),
        },
        yAxis: {
        position: "right",
        type: "value",
        axisLabel: {
            formatter: "{value} BTC",
            color: "#e0e0e0",
        },
        splitLine: {
            lineStyle: {
            color: "#ededed",
            },
        },
        axisLine: {
            lineStyle: {
            color: "#ededed",
            },
        },
        min: 0,
        max: 800,
        },
        series: [
        {
            id: "power",
            name: "Some title",
            type: "line",
            smooth: true,
            showSymbol: false,
            symbolSize: 6,
            symbol: "circle",
            lineStyle: {
            // color: colors.GREEN, // NEED TO MAP THIS VALUE FOR EVERY SECTION
            // shadowColor: colors.GREEN, // NEED TO MAP THIS VALUE FOR EVERY SECTION
            shadowBlur: 4,
            },
            itemStyle: {
            color: "#38b5ca",
            },
            areaStyle: {
            normal: {
                // NEED TO MAP THIS VALUE FOR EVERY SECTION
                // color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
                //   offset: 0,
                //   color: 'rgb(255, 255, 255)'
                // }, {
                //   offset: 1,
                //   color: colors.GREEN
                // }]),
                opacity: 0.2,
            },
            },
        },
        ],
        //color: fields.map((f) => f.color),
        grid: { top: 0, left: 0, right: 0, bottom: 0 },
        legend: { show: false },
        visualMap: {
        type: "piecewise",
        show: false,
        dimension: 1,
        pieces: [
            {
            lte: 251,
            color: "rgb(11, 190, 235)",
            },
            {
            gt: 251,
            lte: 302,
            color: "rgb(255, 197, 0)",
            },
            {
            gt: 302,
            lte: 492,
            color: "rgb(251, 139, 4)",
            },
            {
            gt: 492,
            color: "rgb(237, 31, 143)",
            },
        ],
        },
    };

    const lineChart = echarts.init(element, "sauce", { renderer: "svg" });
    lineChart.setOption(options);
    chartRefs.add(new WeakRef(lineChart));
    return lineChart;
    }

    function bindLineChartNew(lineChart, renderer)
    {
        const dataPoints = defaultLineChartLen;
        let lastRender = 0;
        const fields = [
            {
            id: "power",
            name: "Power",
            color: "rgb(255,255,255)",
            domain: [0, 1000],
            rangeAlpha: [0.4, 1],
            points: [],
            get: (x) => x.state.power || 0,
            fmt: (x) => H.power(x, { seperator: " ", suffix: true }),
            },
        ];
        renderer.addCallback((data) => {
            const now = Date.now();
            if (now - lastRender < 900)
                return;
            lastRender = now;
            if (data && data.state) {
                for (const x of fields) {
                x.points.push(x.get(data));
                while (x.points.length > dataPoints) {
                    x.points.shift();
                }
                }
            }
            lineChart.setOption({
                xAxis: [
                {
                    data: Array.from(sauce.data.range(dataPoints)),
                },
                ],
                series: fields.map((field) => ({
                data: field.points,
                name: typeof field.name === "function" ? field.name() : field.name,
                })),
            });
        });
    }

EDIT: updated code after trying to adapt answer (whole area changes instead of particular sections):

var seriesList = [];
var filteredDatasets = [];
var points = [];

async function createLineChartNew(element) {
  echarts.registerTheme("sauce", theme.getTheme("dynamic"));
  addEventListener("resize", resizeCharts);
  
  const dataPoints = defaultLineChartLen;
  var options = {
    dataset: filteredDatasets,
    title: {
        show: false,
    },
    xAxis: {
        type: 'category',
      boundaryGap: false,
      data: Array.from(new Array(dataPoints)).map((x, i) => i),
    },
    yAxis: {
      type: "value",
      axisLabel: {
        formatter: "{value} W",
      },
    },
    series: seriesList,
    grid: { top: 0, left: 0, right: 0, bottom: 0 },
  };

  const lineChart = echarts.init(element, "sauce", { renderer: "svg" });
  lineChart.setOption(options);
  chartRefs.add(new WeakRef(lineChart));
  return lineChart;
}

var colors = {
    ftp: 'rgb(11, 190, 235)',
    map: 'rgb(255, 197, 0)',
    ac: 'rgb(251, 139, 4)',
    nm: 'rgb(237, 31, 143)'
};

function bindLineChartNew(lineChart, renderer)
{
    const dataPoints = defaultLineChartLen;
    let lastRender = 0;
    renderer.addCallback((data) => {
        // don't render too often
        const now = Date.now();
        if (now - lastRender < 900) return;
        lastRender = now;

        // skip if there is no data
        if (!data || !data.state) return;

        let power = data.state.power || 0;

        // add a new data point, then drop front until desired length
        var x = 0;
        if (points.length > 0) {
            x = points.at(-1).x + 1;
        }
        points.push({x: x, y: power});
        while (points.length > dataPoints) points.shift();

        while (filteredDatasets.length > 0) filteredDatasets.shift();

        for (let ix = 0; ix < points.length - 1; ++ix)
        {
            const datasetId = ix;
            const datasetSliced = points.slice(ix, ix + 2);
            
            filteredDatasets.push({
                id: datasetId,
                source: datasetSliced,
            });

            if (power <= 240)
            {
                seriesList.push({
                    type: 'line',
                    datasetId: datasetId,
                    color: 'green',
                    areaStyle: {color: 'lightgreen'}
                });
            }
            else if (power <= 302)
            {
                seriesList.push({
                    type: 'line',
                    datasetId: datasetId,
                    color: 'red',
                    areaStyle: {color: 'rgb(255,173,177)'}
                });
            }
            else if (power <= 492)
            {
                seriesList.push({
                    type: 'line',
                    datasetId: datasetId,
                    color: 'green',
                    areaStyle: {color: 'lightgreen'}
                });
            }
            else
            {
                seriesList.push({
                    type: 'line',
                    datasetId: datasetId,
                    color: 'red',
                    areaStyle: {color: 'rgb(255,173,177)'}
                });
            }
        }

        while (seriesList.length > dataPoints) seriesList.shift();

        // now update the line chart
        lineChart.setOption({
            dataset: filteredDatasets,
            xAxis: [
            {
                data: Array.from(sauce.data.range(dataPoints)),
            },
            ],
            series: seriesList,
        });
    });
}

Solution

  • Unfortunately it seems like in visual map there is no access to the line property areaStyle. Thats why I currently dont see a "clean" solution.

    What you could do is precompute parts of your line that should be colored differently and build partial lines for each interval which can be styled individually. I modified this official example to show how it can be done here.

    Code:

    import * as echarts from 'echarts';
    
    var chartDom = document.getElementById('main');
    var myChart = echarts.init(chartDom);
    var option;
    
    const data = [
      {x: 0, y: 300}, {x: 1, y: 280}, {x: 2, y: 250}, {x: 3, y: 260}, {x: 4, y: 270}, {x: 5, y: 300}, {x: 6, y: 550}, {x: 7, y: 500},
      {x: 8, y: 400}, {x: 9, y: 390}, {x: 10, y: 380}, {x: 11, y: 390}, {x: 12, y: 400}, {x: 13, y: 500}, {x: 14, y: 600}, {x: 15, y: 750},
      {x: 16, y: 800}, {x: 17, y: 700}, {x: 18, y: 600}, {x: 19, y: 400}
    ];
    
    const seriesList = [];
    const filteredDatasets = [];
    
    function createSeriesList(data) {
      for (let index = 0; index < data.length - 1; index++) {
        const datasetId = 'dataset_' + index;
        const datasetSliced = data.slice(index, index + 2);
        filteredDatasets.push({
          id: datasetId,
          source: datasetSliced
        });
        if (index < 6 || index > 16 || (index > 7 && index < 14)) {
          seriesList.push({
            type: 'line',
            datasetId: datasetId,
            color: 'green',
            areaStyle: { color: 'lightgreen' }
          });
        } else {
          seriesList.push({
            type: 'line',
            datasetId: datasetId,
            color: 'red',
            areaStyle: { color: 'rgba(255, 173, 177)' }
          });
        }
      }
    }
    createSeriesList(data);
    
    option = {
      dataset: filteredDatasets,
      title: {
        text: 'Distribution of Electricity',
        subtext: 'Fake Data'
      },
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'cross'
        }
      },
      toolbox: {
        show: true,
        feature: {
          saveAsImage: {}
        }
      },
      xAxis: {
        type: 'category',
        boundaryGap: false,
        // prettier-ignore
        data: ['00:00', '01:15', '02:30', '03:45', '05:00', '06:15', '07:30', '08:45', '10:00', '11:15', '12:30', '13:45', '15:00', '16:15', '17:30', '18:45', '20:00', '21:15', '22:30', '23:45']
      },
      yAxis: {
        type: 'value',
        axisLabel: {
          formatter: '{value} W'
        },
        axisPointer: {
          snap: true
        }
      },
      series: seriesList
    };
    
    option && myChart.setOption(option);
    
    

    Edit

    Here is an example with dynamic data. I hope it helps.

    Code:

    import * as echarts from 'echarts';
    
    var chartDom = document.getElementById('main');
    var myChart = echarts.init(chartDom);
    var option;
    
    const dataPoints = 50;
    const seriesList = [];
    const filteredDatasets = [];
    
    option = {
      title: {
        show: false
      },
      dataset: filteredDatasets,
      xAxis: {
        type: 'category',
        boundaryGap: false,
        data: []
      },
      yAxis: {
        type: 'value',
        axisLabel: {
          formatter: '{value} W'
        }
      },
      series: seriesList
    };
    
    let index = 1;
    let lastPoint = { x: 0, power: 300 };
    setInterval(function () {
      const power = lastPoint.power + (Math.random() * 60 - 30); // random value in [-30, 30]
      const point = { x: index, power: power };
    
      if (filteredDatasets.length > dataPoints) {
        filteredDatasets.shift();
        seriesList.shift();
      }
    
      filteredDatasets.push({
        id: index,
        source: [lastPoint, point]
      });
    
      if (power <= 240) {
        seriesList.push({
          type: 'line',
          datasetId: index,
          color: 'green',
          areaStyle: { color: 'lightgreen' }
        });
      } else if (power <= 302) {
        seriesList.push({
          type: 'line',
          datasetId: index,
          color: 'red',
          areaStyle: { color: 'rgb(255,173,177)' }
        });
      } else if (power <= 492) {
        seriesList.push({
          type: 'line',
          datasetId: index,
          color: 'green',
          areaStyle: { color: 'lightgreen' }
        });
      } else {
        seriesList.push({
          type: 'line',
          datasetId: index,
          color: 'red',
          areaStyle: { color: 'rgb(255,173,177)' }
        });
      }
    
      // now update the line chart
      myChart.setOption({
        dataset: filteredDatasets,
        series: seriesList
      });
    
      index++;
      lastPoint = point;
    }, 500);
    
    option && myChart.setOption(option);