Search code examples
javascriptchartsecharts

Changing color of the candlestick inside marked area in Echarts


I have created a candlestick chart using Echarts version 5.4.3. I have created 2 mark areas between 0-10 and 30-40. I would like to make the candlestick red inside marked area like this: expected result Here is my code:

option = {
  xAxis: {
    data: ['A', 'B', 'C']
  },
  yAxis: {},
  visualMap: [
      {
        show: true,
        type: 'piecewise',
        seriesIndex: 0,
        pieces: [
          {
            gt: 40,
            color: '#c37976',
            color0: "#c37976",
            borderColor: undefined,
            borderColor0: undefined
          },
          {
            gt: 10,
            lte: 30,
            color: '#808080',
            color0: "#808080",
            borderColor: undefined,
            borderColor0: undefined
          },
          {
            lte: 10,
            color: '#c37976',
               color0: "#c37976",
            borderColor: undefined,
            borderColor0: undefined
          }
        ]
      }
    ],
  series: [{
      type: 'candlestick',
      data: [
        [38, 15, 38, 15],
        [40, 20, 40, 30],
        [0, 15, 0, 15],
      ],
       itemStyle: {
            color: "#808080",
            color0: "#808080",
            borderColor: undefined,
            borderColor0: undefined
          },
       markArea: {
        itemStyle: {
          color: 'rgba(255, 173, 177, 0.4)'
        },
        data: [
          [
            {
              yAxis: 40
            },
            {
              yAxis: 30
            }
          ],
          [
            {
              yAxis: 0,
            },
            {
              yAxis: 10
            }
          ]
        ]
      }
  }
      ],
        graph: {
    color : "rgb(128,128,128)",
  }
  
};

Could you help me? Thank you so much in advance.

I tried using the visual map however it didn't work.


Solution

  • VisualMaps do not work the way you think here. The visualMap is applied to the whole series item if the condition is met, not just to the part which meets the condition.

    I think the best way to achieve what you are trying to do is the follwoing:

    • use multiple stacked bar series
    • the first series represents the space between 0 and the start of the data item
    • the second series represents the space of the data item inside the lower markedArea (if any)
    • the third series represents the space of the data item inbetween the markedAreas
    • the fourth series represents the space of the data item inside the upper markedArea (if any)

    Each series can be styled individually this way. Here is an example:

    const lowerBound = 10;
    const upperBound = 30;
    const dataRanges = [[15, 38], [20, 40], [0, 15]];
    
    // compute data array for each series
    // 1. invisable utility bar such that visable bars start at their respective start value
    // 2. part of the bar which is under lowerBound
    // 3. part of the bar which is inbetween lowerBound and upperBound
    // 4. part of the bar which is above upperBound
    function computeData(dataRanges) {
      const data = [[], [], [], []];
      
      for (const range of dataRanges) {
        const data1 = range[0];
        data[0].push(data1);
        
        let data2 = 0;
        if (range[0] < lowerBound) {
          data2 = lowerBound - range[0];
        }
        data[1].push(data2);
        
        let data3 = Math.min(upperBound, range[1]) - Math.max(lowerBound, range[0]);
        data[2].push(data3);
        
        let data4 = 0;
        if (range[1] > upperBound) {
          data4 = range[1] - upperBound;
        }
        data[3].push(data4);
      }
      
      return data;
    }
    
    const data = computeData(dataRanges);
    
    option = {
      xAxis: {
        type: 'category',
        data: ['A', 'B', 'C']
      },
      yAxis: {
        type: 'value'
      },
    
      series: [
        {
          name: 'starting point',
          type: 'bar',
          stack: 'stack',
          itemStyle: {
            borderColor: 'transparent',
            color: 'transparent'
          },
          silent: true,
          data: data[0],
          markArea: {
            itemStyle: {
              color: 'rgba(255, 173, 177, 0.4)'
            },
            data: [
              [{yAxis: 40}, {yAxis: upperBound}],
              [{yAxis: 0}, {yAxis: lowerBound}]
            ]
          }
        },
        {
          name: 'lower bound',
          type: 'bar',
          stack: 'stack',
          data: data[1],
          itemStyle: {color: '#c37976'}
        },
        {
          name: 'inbetween',
          type: 'bar',
          stack: 'stack',
          data: data[2],
          itemStyle: {color: '#808080'}
        },
        {
          name: 'upper bound',
          type: 'bar',
          stack: 'stack',
          data: data[3],
          itemStyle: {color: '#c37976'}
        }
      ]
    };
    

    Note, that the current implementation only works for data items >= 0 and you might run into difficulties if you want to use tooltip (which might be solvable by adding more utility series).


    EDIT:

    Here is a solution for an arbitrary number of areas which works also for negative values. Note, that the code is not very pretty.

    Exmaple:

    const dataRanges = [[15, 38], [-16, 18], [0, 15], [-3,7], [35, 75], [-15, -30]];
    const areas = [[-5,10], [30, 40], [-20, -12], [70, 90]];
    
    function lowToFirstPosition(list) {
      for (const item of list) {
        if (item[0] <= item[1]) continue;
        
        const lower = item[1];
        item[1] = item[0];
        item[0] = lower;
      }
    }
    lowToFirstPosition(dataRanges);
    lowToFirstPosition(areas);
    
    // sort areas and group into positive and negative
    const negativeAreas = [];
    const positiveAreas = [];
    for (const area of areas) {
      if (area[0] < 0 && area[1] > 0) {
        positiveAreas.push([0, area[1]]);
        negativeAreas.push([area[0], 0]);
      } else if (area[0] >= 0) {
        positiveAreas.push(area);
      } else if (area[1] <= 0) {
        negativeAreas.push(area);
      }
    }
    negativeAreas.sort((a, b) => b[0] - a[0]);
    positiveAreas.sort((a, b) => a[0] - b[0]);
    
    
    const seriesData = {};
    for (const range of dataRanges) {
      let value = 0;
      if (range[0] > 0) value = range[0];
      addOrInit('start_pos', value);
      
      value = 0;
      if (range[1] < 0) value = range[1];
      addOrInit('start_neg', value);
      
      // positive areas
      let lastArea = 0;
      for (const index in positiveAreas) {
        const area = positiveAreas[index];
        
        value = 0;
        if (range[0] < area[0] && range[1] > lastArea) {
          value = Math.min(area[0], range[1]) - Math.max(lastArea, range[0]);
        }
        addOrInit('under' + index + '_p', value);
        
        
        value = 0;
        if (range[0] < area[1] && range[1] > area[0]) {
          value = Math.min(area[1], range[1]) - Math.max(area[0], range[0]);
        }
        addOrInit('inbetween' + index + '_p', value);
        
        // skip bar representing dataItem above the area if its not the last one
        if (Number(index) + 1 === positiveAreas.length) {
          value = 0
          if (range[1] > area[1]) {
            value = range[1] - Math.max(area[1], range[0]);
          }
          addOrInit('above' + index + '_p', 0);
        }
        
        lastArea = area[1];
      }
      
      // negative areas
      lastArea = 0;
      for (const index in negativeAreas) {
        const area = negativeAreas[index];
        
        value = 0;
        if (range[0] < lastArea && range[1] > area[1]) {
          value = Math.max(area[1], range[0]) - Math.min(lastArea, range[1]);
        }
        addOrInit('above' + index + '_n', value);
        
        
        value = 0;
        if (range[0] < area[1] && range[1] > area[0]) {
          value = Math.max(area[0], range[0]) - Math.min(area[1], range[1]);
        }
        addOrInit('inbetween' + index + '_n', value);
        
        // skip bar representing dataItem under the area if its not the last one
        if (Number(index) + 1 === negativeAreas.length) {
          value = 0;
          if (range[0] < area[0]) {
            value = range[0] - Math.min(area[0], range[1]);
          }
          addOrInit('under' + index + '_n', value);
        }
        
        lastArea = area[0];
      }
    
    }
    
    function addOrInit(key, value) {
      if (seriesData[key] === undefined) {
        seriesData[key] = [value];
      } else {
        seriesData[key].push(value);
      }
    }
    
    
    const markAreas = [];
    for (const area of areas) {
      markAreas.push([{yAxis: area[0]}, {yAxis: area[1]}]);
    }
    
    const seriesList = [];
    seriesList.push({
      name: 'starting point positive',
      type: 'bar',
      stack: 'pos',
      itemStyle: {
        borderColor: 'transparent',
        color: 'transparent'
      },
      silent: true,
      data: seriesData['start_pos'],
      markArea: {
        itemStyle: {
          color: 'rgba(255, 173, 177, 0.4)'
        },
        data: markAreas,
      },
      barGap: -1
    });
    delete seriesData['start_pos'];
    
    seriesList.push({
      name: 'starting point negative',
      type: 'bar',
      stack: 'neg',
      itemStyle: {
        borderColor: 'transparent',
        color: 'transparent'
      },
      silent: true,
      data: seriesData['start_neg'],
      markArea: {
        itemStyle: {
          color: 'rgba(255, 173, 177, 0.4)'
        },
        data: markAreas,
      }
    });
    delete seriesData['start_neg'];
    
    for (const key of Object.keys(seriesData)) {
      let color = '#808080';
      if (key.includes('inbetween')) color = '#c37976';
      
      let stack = 'pos';
      if (key.includes('_n')) stack = 'neg';
      
      seriesList.push({
        type: 'bar',
        stack: stack,
        data: seriesData[key],
        itemStyle: {color: color}
      });
    }
    
    
    option = {
      xAxis: {
        type: 'category',
      },
      yAxis: {
        type: 'value'
      },
      series: seriesList
    };