vue3 echart組件封裝

来源:https://www.cnblogs.com/shapeY/p/18043757
-Advertisement-
Play Games

項目中用到了很多echart圖表,進行了簡單的組件封裝,主要包含以下功能: 創建圖表實例,渲染圖表 支持傳入自定義函數,可拿到圖表實例,實現個性化功能 支持配置更新後圖表自動刷新,可配置是清空後再刷新 loading狀態控制 resize時圖表更新 支持餅圖預設高亮功能 實現 資源引入 echart ...


項目中用到了很多echart圖表,進行了簡單的組件封裝,主要包含以下功能:

  • 創建圖表實例,渲染圖表
  • 支持傳入自定義函數,可拿到圖表實例,實現個性化功能
  • 支持配置更新後圖表自動刷新,可配置是清空後再刷新
  • loading狀態控制
  • resize時圖表更新
  • 支持餅圖預設高亮功能

實現

資源引入

  • echart資源按需引入
  • 第三方組件引入(echarts-liquidfill,水波紋圖表)
/* 即下文中的 @/modules/echartPlugin */

// https://echarts.apache.org/handbook/zh/basics/import#%E6%8C%89%E9%9C%80%E5%BC%95%E5%85%A5-echarts-%E5%9B%BE%E8%A1%A8%E5%92%8C%E7%BB%84%E4%BB%B6
import * as echarts from "echarts/core";
import {
  BarChart,
  // 系列類型的定義尾碼都為 SeriesOption
  BarSeriesOption,
  PieChart,
  PieSeriesOption,
  LineChart,
  LineSeriesOption,
  LinesChart,
  LinesSeriesOption,
  EffectScatterChart,
  EffectScatterSeriesOption,
} from "echarts/charts";
import {
  TitleComponent,
  // 組件類型的定義尾碼都為 ComponentOption
  TitleComponentOption,
  TooltipComponent,
  TooltipComponentOption,
  DatasetComponent,
  DatasetComponentOption,
  GridComponent,
  GridComponentOption,
  DataZoomComponent,
  DataZoomComponentOption,
  LegendComponent,
  LegendComponentOption,
  GeoComponent,
  GeoComponentOption,
} from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
import "echarts-liquidfill";

// 通過 ComposeOption 來組合出一個只有必須組件和圖表的 Option 類型
export type ECOption = echarts.ComposeOption<
  | BarSeriesOption
  | TitleComponentOption
  | TooltipComponentOption
  | GridComponentOption
  | DatasetComponentOption
  | DataZoomComponentOption
  | PieSeriesOption
  | LegendComponentOption
  | GeoComponentOption
  | LinesSeriesOption
  | LineSeriesOption
  | EffectScatterSeriesOption
>;

// https://www.npmjs.com/package/echarts-liquidfill
export interface LiquidFillOption {
  series: {
    type: "liquidFill";
    data: number[];
    color?: string[];
    radius?: string;
    center?: [string, string];
    label?: {
      color?: string;
      insideColor?: string;
      fontSize?: number;
      formatter?: (param: {
        borderColor: string;
        color: string;
        data: number;
        dataIndex: number;
        dataType: undefined;
        name: string;
        value: number;
      }) => string | number;
    };
    shape?:
      | "circle"
      | "rect"
      | "roundRect"
      | "triangle"
      | "diamond"
      | "pin"
      | "arrow";
    [name: string]: unknown;
  }[];
  [name: string]: unknown;
}

// 註冊必須的組件
echarts.use([
  TitleComponent,
  TooltipComponent,
  GridComponent,
  BarChart,
  LinesChart,
  CanvasRenderer,
  DatasetComponent,
  DataZoomComponent,
  PieChart,
  LegendComponent,
  GeoComponent,
  LineChart,
  EffectScatterChart,
]);

export default echarts;

組件封裝

<template>
  <div class="h-echart-wrapper" ref="chartWrapperDom">
    <div class="h-echart" ref="chartDom">loading</div>
  </div>
</template>
<script lang="ts" src="./index.ts"></script>
<style lang="less" scoped>
.h-echart-wrapper {
  height: 100%;
}
.h-echart {
  height: 100%;
  width: 100%;
  text-align: center;
}
</style>
import {
  defineComponent,
  onMounted,
  onUnmounted,
  PropType,
  ref,
  watch,
  toRaw,
} from "vue";
import echarts, { ECOption, LiquidFillOption } from "@/modules/echartPlugin";
import ResizeObserver from "resize-observer-polyfill";

export default defineComponent({
  name: "h-echart",
  props: {
    // echart配置
    options: {
      type: Object as PropType<ECOption | LiquidFillOption>,
      required: true,
    },
    // 餅圖是否需要預設高亮
    needDefaultHighLight: {
      type: Boolean,
      default: false,
    },
    loading: Boolean,
    // 自定義函數,會暴露echart實例出去,可以實現個性化操作
    customFn: Function as PropType<
      (echartInstance: null | echarts.ECharts) => void
    >,
    // 更新圖表之前是否先清空
    clearBeforeUpdate: Boolean,
  },
  setup(props) {
    const chartWrapperDom = ref<null | HTMLElement>(null);
    const chartDom = ref<null | HTMLElement>(null);
    // WARN: echarts5 實例用響應式對象存放時會導致功能tooltip功能異常
    let echartInstance: null | echarts.ECharts = null;
    let chartWrapperResize: null | ResizeObserver = null;
    let highlightName: string | null = null;
    let firstRender = true;

    const setOptions = (options?: ECOption | LiquidFillOption) => {
      echartInstance &&
        options &&
        echartInstance.setOption(toRaw(options), {
          notMerge: true,
        });

      if (props.needDefaultHighLight && firstRender) {
        firstRender = false;
        const _options = props.options as ECOption;

        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        if (_options.series && _options.series[0] && _options.series[0].data) {
          // eslint-disable-next-line @typescript-eslint/ban-ts-comment
          // @ts-ignore
          const name = _options.series[0].data[0].name as string;

          setTimeout(() => {
            // 預設高亮
            echartInstance &&
              echartInstance.dispatchAction({
                type: "highlight",
                seriesIndex: 0,
                name,
              });

            highlightName = name;
          }, 600);
        }
      }
    };

    watch(
      () => props.loading,
      (newLoading) => {
        if (newLoading !== undefined && echartInstance) {
          newLoading
            ? echartInstance.showLoading({
                textColor: "rgb(255 255 255 / 0%)",
                showSpinner: false,
                zlevel: 0,
              })
            : echartInstance.hideLoading();
        }
      }
    );

    const init = () => {
      chartDom.value && (echartInstance = echarts.init(chartDom.value));

      props.customFn && props.customFn(echartInstance);

      if (props.needDefaultHighLight && echartInstance) {
        echartInstance.on("mouseover", function (e) {
          if (e.name !== highlightName) {
            echartInstance!.dispatchAction({
              type: "downplay",
              seriesIndex: 0,
              name: highlightName,
            });
          }
        });

        echartInstance.on("mouseout", function (e) {
          highlightName = e.name;
          echartInstance!.dispatchAction({
            type: "highlight",
            seriesIndex: 0,
            name: e.name,
          });
        });
      }
      setOptions(props.options);
    };

    onMounted(() => {
      // 初始化圖表實例
      setTimeout(init, 300);

      // 觀察包裹層變化,進行圖表resize
      if (chartWrapperDom.value) {
        chartWrapperResize = new ResizeObserver(() => {
          echartInstance && echartInstance.resize();
        });

        chartWrapperResize.observe(chartWrapperDom.value);
      }
    });

    // 觀察者清理
    onUnmounted(() => {
      chartWrapperResize?.disconnect();
    });
    watch(
      () => props,
      // 配置變化,重新設置
      (newVal) => {
        if (newVal.clearBeforeUpdate) {
          echartInstance && echartInstance.clear();
        }
        setOptions(toRaw(newVal.options));
      },
      { immediate: true, deep: true }
    );

    return {
      chartDom,
      chartWrapperDom,
    };
  },
});

組件註冊及全局類型聲明

/* ./components/index.ts */
import { App } from "vue";
import HEchart from "./h-echart";
import HIframeKeepAlive from "./h-iframe-keep-alive/index.vue";

export default function useCompoments(app: App<Element>) {
  app &&
    app.component &&
    [
      HEchart,
      HIframeKeepAlive,
    ].forEach((_component) => {
      app.component(_component.name, _component);
    });
}

// 聲明全局組件類型
// https://github.com/johnsoncodehk/volar/blob/master/extensions/vscode-vue-language-features/README.md
declare module "@vue/runtime-core" {
  export interface GlobalComponents {
    HEchart: typeof HEchart;
    HIframeKeepAlive: typeof HIframeKeepAlive;
  }
}
import useCompoments from "./components";

const app = createApp(App).use(router);
tempApp = app;

// 註冊所自定義組件
useCompoments(app);

使用

    <div class="chart-wrapper">
      <h-echart :options="boardPieOptions" needDefaultHighLight />
    </div>
  const boardPieOptions = computed(() => {
    return getBoardPieOptions(props.arrivalNodeStats.types);
  });

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 寫在前面 按照國際慣例,要先聊下生活,吐槽一番,今天是2月14日,也是下午聽老媽說,我才知道! 現在真的是對日期節日已經毫無概念可言,只知道星期幾。 現在已經覺得寫博客也好,學習文章也罷,和寫日記一樣,已經融入到我的生活中,或者更確切的說,變成生活的一部分了。 飯後和老媽閑聊了幾句後,我發現現在真的 ...
  • 概念 RCE(Remote code execution)遠程代碼執行漏洞,RCE又分命令執行和代碼執行。 RCE-遠程代碼執行:遠程執行PHP代碼 RCE-遠程命令執行:遠程執行Linux或者Windows等系統命令。 常見函數有: PHP:eval(),assert(),preg_replace ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、mixin是什麼 Mixin是面向對象程式設計語言中的類,提供了方法的實現。其他類可以訪問mixin類的方法而不必成為其子類 Mixin類通常作為功能模塊使用,在需要該功能時“混入”,有利於代碼復用又避免了多繼承的複雜 Vue中的mi ...
  • 一、react-transition-group 使用 相關技術的使用: React 18 React router v6 React Transition Group 是一個 React 庫,專門用於在 React 應用中管理和處理過渡動畫效果。這個庫提供了一組組件,包括 Transition、C ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、什麼是雙向綁定 我們先從單向綁定切入單向綁定非常簡單,就是把Model綁定到View,當我們用JavaScript代碼更新Model時,View就會自動更新雙向綁定就很容易聯想到了,在單向綁定的基礎上,用戶更新了View,Model的 ...
  • 前言 上周五晚上8點,開開心心的等著產品驗收完畢後就可以順利上線。結果產品突然找到我說要加需求,並且維護這一塊業務的同事已經下班走了,所以只有我來做。雖然內心一萬頭草泥馬在狂奔,但是嘴裡還是一口答應沒問題。由於這一塊業務很複雜並且我也不熟悉,加上還餓著肚子,在梳理代碼邏輯的時候我差點崩潰了。需要修改 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、組件是什麼 回顧以前對組件的定義: 組件就是把圖形、非圖形的各種邏輯均抽象為一個統一的概念(組件)來實現開發的模式,在Vue中每一個.vue文件都可以視為一個組件 組件的優勢 降低整個系統的耦合度,在保持介面不變的情況下,我們可以替換 ...
  • DataGear專業版 1.0.0 已發佈,歡迎試用! http://datagear.tech/pro/ DataGear 支持採用原生的HTML、JavaScript、CSS製作數據可視化看板,也支持導入由npm、vite等前端工具構建的前端程式包。得益於這一特性,可以很容易製作基於three. ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...