echarts实例:可堆叠的立体柱形图+特殊symbol的折线图

记录下工作中使用echarts做出的特殊组件

注意点:柱子颜色不能有透明不然在三角形堆叠的地方会透出来,效果就不好。我这里用了echarts.color.lift给了一个相近的亮色。可以改成自定义的两个颜色做左侧和右侧的立体效果更好。

import 复制代码
import { COLOR_GREEN, FONT_SIZE } from './createChart'

let props = {
  // labelDistance: {
  //   type: Array,
  //   default: () => [FONT_SIZE / 2, 0],
  // },
  areaColorArray: {
    type: Array,
    default: () => ['#248BD2', COLOR_GREEN, '#FFE300', '#248BD2', COLOR_GREEN, '#FFE300'],
  },
  barWidth: {
    type: Number,
    default: 50,
  },
  lineWidth: {
    type: Number,
    default: 5,
  },
  propData: {
    type: Array,
    default: () => [
      { name: '2021', value1: 451.844, value2: 337.342, value3: 184.088, value4: 61, value5: 26, value6: 8 },
      { name: '2022', value1: 451.844, value2: 337.342, value3: 184.088, value4: 61, value5: 26, value6: 8 },
      { name: '2023', value1: 465.456, value2: 350.0438, value3: 184.088, value4: 61, value5: 26, value6: 8 },
      { name: '2024', value1: 476.9401, value2: 336.1348, value3: 184.088, value4: 63, value5: 26, value6: 8 },
      { name: '2025', value1: 484.2852, value2: 402.1726, value3: 184.088, value4: 64, value5: 26, value6: 8 },
    ],
  },
  legendData: {
    type: Array,
    default: () => [
      { name: '35kV长度', type: 'bar', unit: 'KM', key: 'value1' },
      { name: '110kV长度', type: 'bar', unit: 'KM', key: 'value2' },
      { name: '220kV长度', type: 'bar', unit: 'KM', key: 'value3' },
      { name: '35kV条数', type: 'line', unit: '条', key: 'value4' },
      { name: '110kV条数', type: 'line', unit: '条', key: 'value5' },
      { name: '220kV条数', type: 'line', unit: '条', key: 'value6' },
    ],
  },

}



export default defineComponent({
  props,
  data() {
    return {}
  },
  created() {},
  mounted() {
    this.init()
    this.$watch(
      () => this.$props, // 监听整个 props 对象
      () => {
        this.init()
      },
      { deep: true, immediate: false },
    )
  },
  beforeDestroy() {
    this.chart?.dispose?.()
  },
  methods: {
    init() {
      const textStyle = {
        fontSize: FONT_SIZE,
        color: '#fff',
        // formatter: ({ value }) => value.toFixed(2),
      }
      const lineStyle = {
        color: '#fff',
        width: 1,
      }
      const legendData = this.legendData

      const option = {
        legend: [
          {
            // 第一个图例 - 用于柱状图
            show: true,
            top: '0%',

            left: '3%',
            itemWidth: 40,
            itemHeight: 40,
            itemGap: 25,
            data: legendData
              .filter((item) => item.type === 'bar')
              .map((item, index) => ({
                name: item.name,
                itemStyle: {
                  color: this.areaColorArray[index] || '#FFFFFF', // 设置图例图标颜色
                },
              })),
            formatter: (name) => {
              return `{text|${name}}`
            },
            textStyle: {
              ...textStyle,
              padding: [0, 10],
              lineHeight: 1, // 增加行高
              rich: {
                text: {
                  fontSize: 36,
                  color: '#fff',
                  padding: [5, 0, 0, 0], // 只移动文字
                },
              },
            },
          },
          {
            // 第二个图例 - 用于折线图
            show: true,
            top: '0%',
            right: '3%',
            itemWidth: 40,
            itemHeight: 10,
            icon: 'rect',
            itemGap: 25,
            data: legendData
              .filter((item) => item.type === 'bar')
              .map((item, index) => ({
                name: item.name,
                itemStyle: {
                  color: this.areaColorArray[index + 3] || '#FFFFFF', // 设置图例图标颜色
                },
              })), // 过滤出折线图系列的图例
            textStyle: {
              ...textStyle,
              padding: [0, 10],
            },
            itemStyle: {
              borderWidth: 0,
              borderColor: 'transparent',
            },
          },
        ],
        tooltip: {
          show: true,
          trigger: 'axis',
          axisPointer: {
            type: 'none',
          },
          appendToBody: true,
          symbolSize: 100,
          formatter: (params) => {
            if (!(params || params[0])) return
            let result = '<div class="chart-tooltip">' + params[0].axisValue + '<br>'
            params.forEach((item, index) => {
              result += `
              <div class="${item.componentSubType}">
              <span class="icon" style="background:${this.areaColorArray[index]}"></span>
              <span class="name">${item.seriesName}:</span>
              <span class="value" style="color:${this.areaColorArray[index]}">${item.value}
              <span class="unit" style="color:#fff">${item.componentSubType === 'bar' ? 'kM' : '条'}</sapn>
              </span>
              </div>`
            })
            result += '</div >'
            return result
          },
          textStyle: {
            fontSize: 50,
            color: '#fff',
          },
          backgroundColor: '#0C3E5F',
          borderColor: '#0CB6FF',
          borderWidth: 4,
        },
        grid: {
          left: '8%',
          right: '6%',
          bottom: '6%',
          top: '15%',
          containLabel: false,
        },
        xAxis: [
          {
            type: 'category',
            axisTick: {
              show: false,
              alignWithLabel: true,
              lineStyle,
            },
            axisLabel: {
              show: true,
              ...textStyle,
              margin: 25,
            },

            axisLine: {
              lineStyle,
            },
            splitLine: {
              show: false,
            },
            data: this.propData.map((item) => item.name),
          },
          {
            type: 'category',
            axisTick: {
              show: false,
            },
            axisLabel: {
              show: false,
            },
            axisLine: {
              lineStyle,
            },
            splitLine: {
              show: false,
            },
            data: this.propData.map((item) => item.name),
          },
        ],
        yAxis: [
          {
            type: 'value',
            name: 'KM',
            nameGap: 0,
            nameLocation: 'end',
            position: 'left',
            nameTextStyle: {
              ...textStyle,
              padding: [0, 50, 50, 0], // 上、右、下、左
            },
            axisLine: {
              show: true,
              lineStyle,
            },
            axisLabel: {
              ...textStyle,
              margin: 25,
            },
            axisTick: {
              show: false,
            },
            splitLine: {
              show: false,
            },
          },
          {
            type: 'value',
            name: '条数',
            nameGap: 0,
            nameLocation: 'end',
            position: 'right',
            nameTextStyle: {
              ...textStyle,
              padding: [0, 0, 50, 0], // 上、右、下、左
            },
            axisLine: {
              show: true,
              lineStyle,
            },
            axisLabel: {
              ...textStyle,
              margin: 25,
            },
            axisTick: {
              show: false,
            },
            splitLine: {
              show: false,
            },
          },
        ],
        series: this.getSeries(),
      }
      this.draw(option)
    },
    draw(option) {
      const dom = this.$refs.chart
      if (!this.chart) {
        this.chart = echarts.init(dom, null, {
          renderer: 'canvas',
        })
      }
      this.chart.setOption(option, true)
    },
    getSeries() {
      let series = []
      series = this.legendData.map((item, index) => {
        let seriesItem = {
          name: item.name,
          type: item.type,
          yAxisIndex: item.type === 'bar' ? 0 : 1,
          data: this.propData.map((dataItem) => dataItem[item.key]),
        }
        if (item.type === 'line') {
          seriesItem = {
            ...seriesItem,
            symbol: 'circle',
            symbolSize: 18,
            color: this.areaColorArray[index],
            lineStyle: {
              color: this.areaColorArray[index], // 单独控制线条颜色
              width: this.lineWidth,
            },
            z: 60,
            itemStyle: {
              color: '#fff', // 单独控制线条颜色
              borderColor: this.areaColorArray[index], // 边框颜色
              borderWidth: 5, // 边框宽度
              shadowColor: 'rgba(0, 0, 0, 0.5)', // 阴影颜色(黑色半透明)
              shadowBlur: 5, // 阴影模糊大小
              shadowOffsetX: 0, // 水平偏移
              shadowOffsetY: 0,
            },
          }
        }

        if (item.type === 'bar') {
          seriesItem = {
            ...seriesItem,
            stack: 'total',
            barWidth: this.barWidth,
            color: this.areaColorArray[index],
            itemStyle: {
              color: {
                x: 0,
                y: 0,
                x2: 1,
                y2: 0,
                type: 'linear',
                global: false,
                colorStops: this.getColorStops(this.areaColorArray[index]),
              },
            },
            label: {
              show: true,
              fontSize: 36,
              color: '#fff',
              fontFamily: 'TRENDS',
              position: 'right',
              distance: 15,
              borderWidth: 1, // 边框宽度
              borderRadius: 3, // 圆角边框
              padding: [15, 15, 0, 15], // 内边距
              backgroundColor: 'rgba(0, 0, 0, 0.24)', // 背景色
              borderColor: this.areaColorArray[index], // 边框颜色
            },
          }
        }
        return seriesItem
      })
      series = [...this.getSeriesLine(series), ...series, ...this.getSeriesPictorialBar(series)]

      return series
    },
    getSeriesLine(series) {
      const seriesLine = []
      series.forEach((item) => {
        if (item.type === 'line') {
          seriesLine.push({
            ...item,
            symbol: 'circle',
            tooltip: { show: false },
            symbolSize: 40,
            lineStyle: {
              color: 'transparent', // 单独控制线条颜色
              width: 0,
            },
            itemStyle: {
              color: echarts.color.modifyAlpha(item.color, 0.5), // 单独控制线条颜色
              borderColor: '#fff', // 边框颜色
              borderWidth: 2, // 边框宽度
            },
          })
        }
      })
      return seriesLine
    },
    // 为了3D效果加的立体面
    getSeriesPictorialBar(series) {
      const pictorialBarSeries = []
      const bar_series = series.filter((item) => {
        return item.type === 'bar'
      })
      const maxData = this.propData.map((item) => {
        return item.value1 + item.value2 + item.value3
      })
      const common_config = {
        name: 'top',
        type: 'pictorialBar',
        symbol: 'diamond',
        symbolPosition: 'end',
        tooltip: { show: false },
        symbolSize: [this.barWidth, this.barWidth / 2],
        symbolOffset: [-0, -this.barWidth / 2 + 11],
        z: 40,
        label: {},
        data: maxData,
        itemStyle: {
          borderColor: echarts.color.modifyAlpha('#666666', 0.9),
          borderWidth: 3, // 边框宽度
          borderType: 'solid', // 边框样式,可选:solid, dashed, dotted
        },
      }

      const colors = _.map(bar_series, 'color')
      bar_series.forEach((item, index) => {
        if (index === bar_series.length - 1) {
          pictorialBarSeries.push({
            ...common_config,
            name: 'top',
            color: item.color,
          })
        } else {
          const data = this.getPictorialBarData(bar_series, index)

          const color = colors.reverse()[index] || '#fff'
          pictorialBarSeries.push({
            ...common_config,
            symbolOffset: [-0, -this.barWidth / 2 + 13],
            z: 20,
            color,
            itemStyle: {
              color: {
                x: 0,
                y: 0,
                x2: 1,
                y2: 0,
                type: 'linear',
                global: false,
                colorStops: this.getColorStops(color),
              },
            },
            data,
          })
        }
      })
      // 中间一根竖线
      pictorialBarSeries.push({
        data: maxData,
        type: 'bar',
        barWidth: 3,
        barGap: '-50%',
        tooltip: { show: false },
        itemStyle: {
          color: echarts.color.modifyAlpha('#666666', 0.2),
        },
        z: 30,
        xAxisIndex: 1,
      })
      return pictorialBarSeries
    },

    getPictorialBarData(seriesData, index) {
      return this.getCumulativeData(_.map(seriesData, 'data').reverse(), index)
    },
    getCumulativeData(data, index) {
      // 获取从 index+1 开始的所有数组
      const remainingArrays = data.slice(index + 1)

      if (remainingArrays.length === 0) {
        return []
      }
      const result2 = remainingArrays[0].map((_, index) => remainingArrays.reduce((sum, row) => sum + row[index], 0))
      return result2
    },
    getColorStops(color) {
      return [
        {
          offset: 0,
          color: echarts.color.lift(color, 0.15),
        },
        {
          offset: 0.5,
          color: echarts.color.lift(color, 0.15),
        },
        {
          offset: 0.5,
          color: echarts.color.modifyAlpha(color, 1),
        },
        {
          offset: 1,
          color: echarts.color.modifyAlpha(color, 1),
        },
      ]
    },
  },
})
相关推荐
这个实现不了2 小时前
echarts实例:进度条加描述
echarts
这个实现不了2 小时前
echarts实例:最高最低标识-并列立体柱形图
echarts
这个实现不了2 小时前
echarts实例:双轴水平条形图(菱形和三角形的symbol)
echarts
这个实现不了2 小时前
echarts实例:雷达图做出时钟效果
echarts
这个实现不了2 小时前
echarts实例:圆环内衬图
echarts
B站_计算机毕业设计之家6 天前
电影知识图谱推荐问答系统 | Python Django系统 Neo4j MySQL Echarts 协同过滤 大数据 人工智能 毕业设计源码(建议收藏)✅
人工智能·python·机器学习·django·毕业设计·echarts·知识图谱
吴声子夜歌13 天前
RxJava——Subscriber
android·echarts·rxjava
小白探索世界欧耶!~14 天前
Vue2项目引入sortablejs实现表格行拖曳排序
前端·javascript·vue.js·经验分享·elementui·html·echarts
吴声子夜歌17 天前
RxJava——调度器Scheduler
android·echarts·rxjava