Echart饼图自动轮播效果封装

Echart饼图效果:

未封装轮播效果

饼图组件chart-pie-stats-list.vue

html 复制代码
<template>
  <div class="chart-wrap flex">
    <div class="item one">
      <div class="chart" ref="chartRef"></div>
    </div>
    <div class="item one flex chart-text">
      <div class="flex flex-wrap w-100 column">
        <div
          class="item one flex column pt15 pb15 chart-text-item"
          v-for="(item, index) in dataObj.data"
          :key="index"
        >
          <div class="title flex col-center pb10">
            <div
              class="icon-circle mr10"
              :style="'background:' + color[index]"
            ></div>
            <div class="text-cont">
              <el-tooltip
                :content="item.name.toString()"
                effect="dark"
                placement="top"
              >
                <span class="span-text-over">{{ item.name }}</span>
              </el-tooltip>
            </div>
          </div>
          <div class="value-percent flex-column">
            <div
              class="value flex flex-1 pb10"
              v-if="fields.count"
            >
              <div class="text-cont flex row-right">
                <el-tooltip
                  :content="item.value.toString()"
                  effect="dark"
                  placement="top"
                >
                  <span class="span-text-over"
                    >{{ parseFloat(item.value || 0).toLocaleString()
                    }}<span v-if="fields.unit && unit">{{ unit }}</span></span
                  >
                </el-tooltip>
              </div>
            </div>
            <div
              :style="[{ color: color[index % color.length] }]"
              class="percent flex flex-1 row-left"
              v-if="fields.percent"
            >
              <div class="text-cont flex row-right">
                <el-tooltip
                  :content="item.percent.toString() + '%'"
                  effect="dark"
                  placement="top"
                >
                  <span
                    class="span-text-over"
                    v-if="
                      Number(item.percent) === 100 || Number(item.percent) === 0
                    "
                    :style="'color:#686868'"
                  >
                    {{ item.percent }}%
                  </span>
                  <span class="span-text-over" :style="'color:#686868'" v-else
                    >{{ Number(item.percent) }}%</span
                  >
                </el-tooltip>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import * as echarts from "echarts";
export default {
  props: {
    dataObj: {
      type: Object,
      default() {
        return {};
      },
    },
    title: {
      type: String,
      default: "",
    },
    color: {
      type: Array,
      default: () => ["#409EFF", "#67c23a", "#ebb563"],
    },
    // 字段显示
    fields: {
      type: Object,
      default: () => ({
        count: true,
        unit: true,
        percent: true,
      }),
    },
    // 数据单位
    unit: {
      type: String,
      default: "人次",
    },
    // 自动播放时间间隔,默认2s
    autoTime: {
      type: Number,
      default: 2,
    },
  },
  data() {
    return {
      option: {
        tooltip: {
          trigger: "item",
          formatter: (item) => {
            if (item.data.name) {
              return (
                item.marker +
                item.name +
                " " +
                (this.fields.count && item.value >= 0
                  ? `${item.value}${
                      this.fields.unit && this.unit ? this.unit : ""
                    }`
                  : "") +
                " " +
                (this.fields.percent && item.percent ? item.percent + "%" : "")
              );
            }
          },
        },
        legend: {
          orient: "vertical",
          left: "left",
          show: false,
        },
        series: [
          {
            name: "",
            type: "pie",
            radius: ["78%", "70%"],
            label: {
              show: false,
              position: "center",
            },
            emphasis: {
              label: {
                show: true,
                fontSize: 28,
                fontWeight: "bold",
              },
            },
            labelLine: {
              show: false,
            },
            itemStyle: {
              borderRadius: 10,
            },
            data: [
              // { value: 1048, name: 'Search Engine' },
              // { value: 735, name: 'Direct' },
            ],
          },
        ],
        color: this.color,
      },
      currentIndex: -1,
      myChart: null,
      timer: null,
    };
  },
  watch: {
    dataObj: {
      handler() {
        this.initData();
      },
      deep: true,
    },
  },
  mounted() {
    this.initData();
  },
  methods: {
    initData() {
      this.$set(this.option.series[0], "data", this.dataObj.data);
      if (this.title) {
        this.option.title.text = this.title;
      }
      let chartDom = this.$refs.chartRef;
      this.myChart = echarts.init(chartDom);
      this.option && this.myChart.setOption(this.option);
      let chartTarget = this.$refs.chartRef;
      this.autoPlay();
      chartTarget.addEventListener("mouseenter", () => {
        this.stopLightChart();
      });
      chartTarget.addEventListener("mouseleave", () => {
        this.autoPlay();
      });
    },
    autoPlay() {
      this.autoLightChart();
      this.timer = setInterval(() => {
        this.autoLightChart();
      }, this.autoTime * 1000);
    },
    autoLightChart() {
      let dataLen = this.option.series[0].data.length;
      const textItems = document.querySelectorAll(".chart-text-item");
      // 取消之前高亮的图形
      this.myChart.dispatchAction({
        type: "downplay",
        seriesIndex: 0,
        dataIndex: this.currentIndex,
      });
      this.currentIndex = (this.currentIndex + 1) % dataLen;
      // 高亮当前图形
      this.myChart.dispatchAction({
        type: "highlight",
        seriesIndex: 0,
        dataIndex: this.currentIndex,
      });
      // 显示 tooltip
      this.myChart.dispatchAction({
        type: "showTip",
        seriesIndex: 0,
        dataIndex: this.currentIndex,
      });
      textItems.forEach((item, index) => {
        if (index === this.currentIndex) {
          item.classList.add("active");
        } else {
          item.classList.remove("active");
        }
      });
    },
    stopLightChart() {
      clearInterval(this.timer);
      let dataLen = this.option.series[0].data.length;
      const textItems = document.querySelectorAll(".chart-text-item");
      dataLen.forEach((item, index) => {
        // 取消之前高亮的图形
        this.myChart.dispatchAction({
          type: "downplay",
          seriesIndex: 0,
          dataIndex: index,
        });
      });
      textItems.forEach((item, index) => {
        item.classList.contains("active") && item.classList.remove("active");
      });
    },
  },
  destroyed() {
    this.stopLightChart();
  },
};
</script>
<style lang="scss" scoped>
.chart-wrap {
  width: 100%;

  .chart {
    height: 200px;
    width: 100%;
  }

  .chart-text {
    overflow: hidden;

    .title {
      .icon-circle {
        width: 8px;
        height: 8px;
      }
    }
  }
}

.chart-text-item {
  padding: 20px;
  opacity: 0.4;
  transition: all 0.4s;
  border-radius: 10px;
  transform: scale(0.99);

  &.active {
    opacity: 1;
    transform: scale(1.01);
  }
}
</style>

封装轮播效果后

将自动轮播的过程封装为一个js方法调用

tool-pie.js

js 复制代码
/**
 *  echarts tooltip 自动轮播
 *  @param myChart  //初始化echarts的实例
 *  @param num      //类目数量(原因:循环时达到最大值后,使其从头开始循环)
 *  @param time     //轮播间隔时长
 */
export function autoHover(myChart, num = 12, time = 2000, extra = { enable: true, normal: ".chart-text-item", active: 'active' }) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // echarts实例
      let echartsInstance = myChart.node;
      // 开启自动轮播
      let gInterVal = startAuto(myChart.chart, num, time, extra)

      // /* 鼠标位于触发点关闭自动轮播,离开触发点开启自动轮播 */
      echartsInstance.addEventListener("mouseenter", function () {
        stopAuto(gInterVal, myChart.chart, num, extra)
      })
      echartsInstance.addEventListener("mouseleave", function () {
        gInterVal = startAuto(myChart.chart, num, time, extra)
      })

      let dispose = () => {
        return stopAuto(gInterVal, myChart.chart, num, extra)
      }
      resolve(dispose)
    })
  })
}

// 开启自动轮播
function startAuto(myChart, num, time, extra) {
  let currentIndex = 0
  activeChart(myChart, currentIndex, num, extra)
  let timeTicket = null
  timeTicket && clearInterval(timeTicket)
  timeTicket = setInterval(() => {
    currentIndex = currentIndex + 1
    if (currentIndex >= num) {
      currentIndex = 0
    }
    activeChart(myChart, currentIndex, num, extra)
  }, time)
  return timeTicket
}

function activeChart(myChart, currentIndex, num, extra) {
  // 取消之前高亮的图形
  for (let i = 0; i < num; i++) {
    myChart.dispatchAction({
      type: 'downplay',
      seriesIndex: 0,
      dataIndex: i
    })
  }
  // 高亮当前图形
  myChart.dispatchAction({
    type: 'highlight',
    seriesIndex: 0,
    dataIndex: currentIndex
  })

  // 显示 tooltip
  myChart.dispatchAction({
    type: "showTip",
    seriesIndex: 0,
    dataIndex: currentIndex
  });

  // 建议同一个项目统一风格,方便处理
  if(extra?.enable){
    const textItems = document.querySelectorAll(extra.normal);
    textItems.forEach((item, index) => {
      if (index === currentIndex) {
        item.classList.add(extra.active);
      } else {
        item.classList.remove(extra.active);
      }
    });
  }

}

// 销毁自动轮播
function stopAuto(timeTicket, myChart, num, extra) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < num; i++) {
      myChart.dispatchAction({
        type: 'downplay',
        seriesIndex: 0,
        dataIndex: i
      })
    }

    // 建议同一个项目统一风格,方便处理
    if(extra?.enable){
      const textItems = document.querySelectorAll(extra.normal);
      textItems.forEach((item, index) => {
        item.classList.contains(extra.active) && item.classList.remove(extra.active);
      });
    }

    timeTicket && clearInterval(timeTicket)
    resolve()
  })

}

export default {
  autoHover
}

使用 tool-pie.js 的 chart-pie-stats-list.vue

html 复制代码
<!--
 * @Descripttion: 会员饼图
 * @Author: wang pingqi
 * @Date: 2023-11-15 14:46:52
 * @LastEditors: wang ping qi
 * @LastEditTime: 2025-07-14 16:06:02
-->
<template>
  <div class="chart-wrap flex">
    <div class="item one">
      <div class="chart" ref="chartRef"></div>
    </div>
    <div class="item one flex chart-text">
      <div class="flex flex-wrap w-100 column">
        <div
          class="item one flex column pt15 pb15 chart-text-item"
          v-for="(item, index) in dataObj.data"
          :key="index"
        >
          <div class="title flex col-center pb10">
            <div
              class="icon-circle mr10"
              :style="'background:' + color[index]"
            ></div>
            <div class="text-cont">
              <el-tooltip
                :content="item.name.toString()"
                effect="dark"
                placement="top"
              >
                <span class="span-text-over">{{ item.name }}</span>
              </el-tooltip>
            </div>
          </div>
          <div class="value-percent flex-column">
            <div
              class="value flex flex-1 pb10"
              v-if="fields.count"
            >
              <div class="text-cont flex row-right">
                <el-tooltip
                  :content="item.value.toString()"
                  effect="dark"
                  placement="top"
                >
                  <span class="span-text-over"
                    >{{ parseFloat(item.value || 0).toLocaleString()
                    }}<span v-if="fields.unit && unit">{{ unit }}</span></span
                  >
                </el-tooltip>
              </div>
            </div>
            <div
              :style="[{ color: color[index % color.length] }]"
              class="percent flex flex-1 row-left"
              v-if="fields.percent"
            >
              <div class="text-cont flex row-right">
                <el-tooltip
                  :content="item.percent.toString() + '%'"
                  effect="dark"
                  placement="top"
                >
                  <span
                    class="span-text-over"
                    v-if="
                      Number(item.percent) === 100 || Number(item.percent) === 0
                    "
                    :style="'color:#686868'"
                  >
                    {{ item.percent }}%
                  </span>
                  <span class="span-text-over" :style="'color:#686868'" v-else
                    >{{ Number(item.percent) }}%</span
                  >
                </el-tooltip>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import tools from "@/utils/tool-pie";
import * as echarts from "echarts";
export default {
  props: {
    dataObj: {
      type: Object,
      default() {
        return {};
      },
    },
    title: {
      type: String,
      default: "",
    },
    color: {
      type: Array,
      default: () => ["#409EFF", "#67c23a", "#ebb563"],
    },
    // 字段显示
    fields: {
      type: Object,
      default: () => ({
        count: true,
        unit: true,
        percent: true,
      }),
    },
    // 数据单位
    unit: {
      type: String,
      default: "人次",
    },
    // 自动播放时间间隔,默认2s
    autoTime: {
      type: Number,
      default: 2,
    },
  },
  data() {
    return {
      option: {
        tooltip: {
          trigger: "item",
          formatter: (item) => {
            if (item.data.name) {
              return (
                item.marker +
                item.name +
                " " +
                (this.fields.count && item.value >= 0
                  ? `${item.value}${
                      this.fields.unit && this.unit ? this.unit : ""
                    }`
                  : "") +
                " " +
                (this.fields.percent && item.percent ? item.percent + "%" : "")
              );
            }
          },
        },
        legend: {
          orient: "vertical",
          left: "left",
          show: false,
        },
        series: [
          {
            name: "",
            type: "pie",
            radius: ["78%", "70%"],
            label: {
              show: false,
              position: "center",
            },
            emphasis: {
              label: {
                show: true,
                fontSize: 28,
                fontWeight: "bold",
              },
            },
            labelLine: {
              show: false,
            },
            itemStyle: {
              borderRadius: 10,
            },
            data: [
              // { value: 1048, name: 'Search Engine' },
              // { value: 735, name: 'Direct' },
            ],
          },
        ],
        color: this.color,
      },
      myChart: null,
      tools: null,
    };
  },
  watch: {
    dataObj: {
      handler() {
        this.initData();
      },
      deep: true,
    },
  },
  mounted() {
    this.initData();
  },
  methods: {
    initData() {
      this.$set(this.option.series[0], "data", this.dataObj.data);
      if (this.title) {
        this.option.title.text = this.title;
      }
      let chartDom = this.$refs.chartRef;
      this.myChart = echarts.init(chartDom);
      this.option && this.myChart.setOption(this.option);

      if(this.$refs.chartRef && !this.tools ){
        let dataLength = this.option.series[0].data.length || 0
        tools.autoHover(
          {
            node : this.$refs.chartRef,
            chart : this.myChart,
          },
          dataLength,
          2000
        ).then((tools) => {
          this.tools = tools
        });
      }
    }
  },
  destroyed() {
    if (this.tools) this.tools()
  },
};
</script>

<style lang="scss" scoped>
.chart-wrap {
  width: 100%;

  .chart {
    height: 200px;
    width: 100%;
  }

  .chart-text {
    overflow: hidden;

    .title {
      .icon-circle {
        width: 8px;
        height: 8px;
      }
    }
  }
}

.chart-text-item {
  padding: 20px;
  opacity: 0.4;
  transition: all 0.4s;
  border-radius: 10px;
  transform: scale(0.99);

  &.active {
    opacity: 1;
    transform: scale(1.01);
  }
}
</style>

调用 chart-pie-stats-list.vue

html 复制代码
<template>
    <chartPieStatsList :dataObj="chartData2" />
</template>

<script>
import chartPieStatsList from "./components/chart-pie-stats-list";
export default {
  data() {
    return {
      chartData2: {
        loading: true,
        title: "性别比例",
        type: "pie",
        radius: ["50%", "50%"],
        data: [
          {
            value: 0,
            percent: 0,
            name: "男",
          },
          {
            value: 0,
            percent: 0,
            name: "女",
          },
        ],
      },
    }
  }
}
</script>
相关推荐
chao_7891 小时前
frame 与新窗口切换操作【selenium 】
前端·javascript·css·selenium·测试工具·自动化·html
天蓝色的鱼鱼1 小时前
从零实现浏览器摄像头控制与视频录制:基于原生 JavaScript 的完整指南
前端·javascript
三原1 小时前
7000块帮朋友做了2个小程序加一个后台管理系统,值不值?
前端·vue.js·微信小程序
白仑色2 小时前
完整 Spring Boot + Vue 登录系统
vue.js·spring boot·后端
阳火锅2 小时前
Vue 开发者的外挂工具:配置一个 JSON,自动造出一整套页面!
javascript·vue.js·面试
每天吃饭的羊2 小时前
react中为啥使用剪头函数
前端·javascript·react.js
多啦C梦a3 小时前
【适合小白篇】什么是 SPA?前端路由到底在路由个啥?我来给你聊透!
前端·javascript·架构
薛定谔的算法3 小时前
《长安的荔枝·事件流版》——一颗荔枝引发的“冒泡惨案”
前端·javascript·编程语言
轻语呢喃3 小时前
每日LeetCode : 两数相加--链表操作与进位的经典处理
javascript·算法
G_whang3 小时前
jenkins部署前端vue项目使用Docker+Jenkinsfile方式
前端·vue.js·jenkins