The overlapping and stacking of echarts’ histogram realizes the display of two bars and the previous difference display.

renderings

Main idea

Prepare three cylinders (original plan, actual progress, difference)
The original plan and actual progress are set to overlap

 {<!-- -->
            barWidth: 20,
            // yAxisIndex: 1,
            z: 1,
            name: 'original plan',
            type: 'bar',
            stack: 'ab',
            emphasis: {<!-- --> // Click on the cylinder and the color of other cylinders will become lighter.
              disabled: true
            },
            label: {<!-- -->
              show: true
            },
            itemStyle: {<!-- -->
              emphasis: {<!-- -->
                barBorderRadius: 10
              },
              normal: {<!-- -->
                barBorderRadius: 10
              }
            },
            data: this.planProcessData.map((item) => {<!-- -->
              return {<!-- -->
                value: item,
                label: {<!-- -->
                  show: !(item < 10),
                  formatter: item + '%',
                  position: 'insideRight',
                  textStyle: {<!-- -->
                    color: '#262626',
                    fontSize: 12
                  }
                }
              }
            })
          },
 {<!-- -->
            barGap: '-100%', /* can overlap*/
            barWidth: 20,
            yAxisIndex: 0,
            z: 2,
            itemStyle: {<!-- -->
              emphasis: {<!-- -->
                barBorderRadius: 10
              },
              normal: {<!-- -->
                barBorderRadius: 10
              }
            },
            data: this.realityProcessData.map((item) => {<!-- -->
              return {<!-- -->
                value: item,
                label: {<!-- -->
                  show: true,
                  formatter: item + '%',
                  position: item < 10 ? 'right' : 'insideRight',
                  textStyle: {<!-- -->
                    color: '#262626',
                    fontSize: 12
                  }
                }
              }
            }),
            type: 'bar',
            label: {<!-- -->
              show: true
            },
            emphasis: {<!-- --> // Click on the cylinder and the color of other cylinders will become lighter.
              disabled: true
            },
            name: 'Actual progress'
          }

Set the actual progress barGap: -100%’, and you can overlap it.

Then set the difference cylinder

{<!-- -->
            barWidth: 20,
            // yAxisIndex: 1,
            z: 1,
            stack: 'ab', // This can be stacked by keeping it the same
            data: this.differenceData.map((item) => {<!-- -->
              return {<!-- -->
                value: item,
                label: {<!-- -->
                  show: true,
                  formatter: (params) => {<!-- --> // The core part formatter can be a string or a callback
                    // var that = this
                    // console.log('0904', this.differenceData)
                    if (params.value) {<!-- --> // Splice if the current value exists
                      return '↓' + ' ' + '-' + params.value + '%'
                    } else {<!-- --> // Otherwise return empty
                      return ''
                    }
                  },
                  position: 'insideLeft',
                  textStyle: {<!-- -->
                    color: '#E20000',
                    fontSize: 12
                  }
                }
              }
            }),
            type: 'bar',
            emphasis: {<!-- --> // Click on the cylinder and the color of other cylinders will become lighter.
              disabled: true
            },
            label: {<!-- -->
              show: true
            },
            name: 'difference'
          },

To stack the differences with the original plan, they set stack: ab’, which can be stacked as they are, and then change the background color of the difference column to white

Then change the position of the displayed value to insideLeft

You can achieve the above effect

3. If the two overlapping cylinders do not completely overlap

Effect

To set the width in the original plan

In actual progress, the width should be set smaller and the overlap should be 80%.

4. Set the maximum width to 100%, but the content can exceed


This way it can be displayed

5. In series, in formatter, customize pictures

renderings

Then define it in rich

Finally used in formatter

First, import pictures

Supplement

1. If you want to use this here, be sure to use an arrow function

2. If you want to move the mouse to a cylinder, some other cylinders will not be transparent

To be set in each cylinder

3. Add a click event to the bar chart

This cannot be obtained directly in the click event function, you need to let that = this

Complete code

initData() {<!-- -->
      const PieEchartBox = this.$refs.projectChart
      const myPieEchart = this.$echarts.init(PieEchartBox)
      myPieEchart.setOption({<!-- -->
        color: ['#E3F1FF', '#fff', '#1CCBD3'],
        legend: {<!-- -->
          data: ['Original plan', 'Difference', 'Actual progress'],
          show: false
        },
        tooltip: {<!-- -->
          trigger: 'axis',
          extraCssText: 'width:200px',
          valueFormatter: function(value) {<!-- --> // Add content to the value inside
            return value + '%'
          },
          axisPointer: {<!-- -->
            type: 'shadow'
          },
          formatter: "<div style='display:block;word-break: break-all;word-wrap: break-word;white-space:pre-wrap;margin-bottom: 10px'>" + \ '{b}' + '</div>' +
                                "<div style='display: flex;justify-content: space-between;'><div style='display: flex;align-items: center;'><div style='border- radius: 50%;width: 8px;height: 8px;background: #7ECAD3;margin-right: 10px'></div>" + '<span>' + '{a0}' + \ '</span></div><div>{c0}%</div></div>' +
                                "<div style='display: flex;justify-content: space-between;'><div style='display: flex;align-items: center;'><div style='border- radius: 50%;width: 8px;height: 8px;background: #5682cc;margin-right: 10px'></div>" + '<span>' + '{a2}' + \ '</span></div><div>{c2}%</div></div>'
        },
        grid: {<!-- -->
          left: '10',
          right: '6%',
          bottom: '3%',
          top: '2%',
          containLabel: true
        },
        yAxis: [{<!-- -->
          type: 'category',
          axisTick: {<!-- -->
            length: 10
          },
          axisLine: 'none',
          data: this.topicNameData,
          axisLabel: {<!-- -->
            margin: 10,
            interval: 0,
            lineHeight: 14,
            textStyle: {<!-- -->
              fontSize: 12,
              color: '#666666'
            },
            formatter: function(value) {<!-- -->
              var ret = ''// Concatenate and add \\
returned category items
              var valLength = value.length//The number of texts in the X-axis category items
              var maxLength = 10 //Number of displayed text for each item
              var rowN = Math.ceil(valLength / maxLength) //The number of rows that the category item needs to wrap
              if (rowN > 1) {<!-- --> // / If the text of the category item is greater than 3,
                for (var i = 0; i < rowN & amp; & amp; i < 2; i + + ) {<!-- -->
                  var temp = ''// The string intercepted each time
                  var end = ''
                  var start = i * maxLength//Start interception position
                  if (i === 1 & amp; & amp; valLength > 20) {<!-- -->
                    end = start + 9//End interception position
                  } else {<!-- -->
                    end = start + maxLength//End interception position
                  }
                  // You can also add a judgment here to determine whether it is the last line, but it will have no effect if you don't add it, so don't add it.
                  if (i === 1 & amp; & amp; valLength > 20) {<!-- -->
                    temp = value.substring(start, end) + '...'//End interception position
                  } else {<!-- -->
                    temp = value.substring(start, end) + '\\
'// End interception position
                  }
                  ret + = temp // with the final string
                }
                return ret
              } else {<!-- -->
                return value
              }
            }
          }
        },
        {<!-- -->
          show: false,
          type: 'category',
          axisTick: 'none',
          data: [],
          axisLine: 'none'
        }],
        xAxis: {<!-- -->
          type: 'value',
          interval: 10,
          max: 100.001, // in order to display the difference
          axisLabel: {<!-- -->
            show: true,
            textStyle: {<!-- -->
              fontSize: 10
            },
            itemWidth: 123,
            formatter: '{value}%' // Add a percent sign to the Y-axis value
          },
          position: 'top'
        },

        series: [
          {<!-- -->
            barWidth: 24,
            barCategoryGap: '60%',
            // yAxisIndex: 1,
            z: 1,
            name: 'original plan',
            type: 'bar',
            stack: 'ab',
            emphasis: {<!-- --> // Click on the cylinder and the color of other cylinders will become lighter.
              disabled: true
            },
            label: {<!-- -->
              show: true
            },
            itemStyle: {<!-- -->
              emphasis: {<!-- -->
                barBorderRadius: 10
              },
              normal: {<!-- -->
                barBorderRadius: 10
              }
            },
            data: this.planProcessData.map((item) => {<!-- -->
              return {<!-- -->
                value: item,
                label: {<!-- -->
                  show: !(item < 10),
                  formatter: item + '%',
                  position: 'insideRight',
                  textStyle: {<!-- -->
                    color: '#262626',
                    fontSize: 12
                  }
                }
              }
            })
          },
          {<!-- -->
            barWidth: 20,
            barCategoryGap: '60%',
            // yAxisIndex: 1,
            z: 1,
            stack: 'ab', // This can be stacked by keeping it the same
            data: this.differenceData.map((item) => {<!-- -->
              return {<!-- -->
                value: item,
                label: {<!-- -->
                  normal: {<!-- -->
                    show: true,
                    formatter: (params) => {<!-- --> // The core part formatter can be a string or a callback
                      if (params.value) {<!-- --> // Splice if the current value exists
                        return '{imgDown|}' + ' ' + '-' + params.value.toFixed(2) + '%'
                      } else {<!-- --> // Otherwise return empty
                        return ''
                      }
                    },
                    rich: {<!-- -->
                      imgDown: {<!-- -->
                        paddingLeft: 10,
                        backgroundColor: {<!-- -->
                          image: downIcon
                        },
                        width: 8,
                        height: 12
                      }
                    },
                    position: 'insideLeft',
                    textStyle: {<!-- -->
                      color: '#E20000',
                      fontSize: 12
                    }
                  }
                }
              }
            }),
            type: 'bar',
            emphasis: {<!-- --> // Click on the cylinder and the color of other cylinders will become lighter.
              disabled: true
            },
            label: {<!-- -->
              show: true
            },
            name: 'Difference'
          },
          {<!-- -->
            barGap: '-80%', /* can overlap*/
            barWidth: 14,
            barCategoryGap: '60%',
            yAxisIndex: 0,
            z: 2,
            itemStyle: {<!-- -->
              emphasis: {<!-- -->
                barBorderRadius: 10
              },
              normal: {<!-- -->
                barBorderRadius: 10
              }
            },
            data: this.realityProcessData.map((item) => {<!-- -->
              return {<!-- -->
                value: item,
                label: {<!-- -->
                  show: true,
                  formatter: item + '%',
                  position: item < 10 ? 'right' : 'insideRight',
                  textStyle: {<!-- -->
                    color: '#262626',
                    fontSize: 12
                  }
                }
              }
            }),
            type: 'bar',
            label: {<!-- -->
              show: true
            },
            emphasis: {<!-- --> // Click on the cylinder and the color of other cylinders will become lighter.
              disabled: true
            },
            name: 'Actual progress'
          }
        ]
      })
      const that = this
      myPieEchart.on('click', function(param) {<!-- -->
        console.log('1019', that.dictTypeTopicList, param)
        let topicType = ''
        that.dictTypeTopicList.forEach((res) => {<!-- -->
          if (param.name === res.label) {<!-- -->
            topicType = res.value
            return
          }
        })
        that.$router.push({<!-- -->
          path: '/projectDetails',
          query: {<!-- -->
            topicName: param.name,
            topicType: topicType
          }
        })
      })
    }