美文网首页
echarts 图表定制 02

echarts 图表定制 02

作者: opengladv | 来源:发表于2023-04-16 09:36 被阅读0次

    3.5 ECharts.setOption 基本逻辑

    • /src/core/echart.ts 文件中的 ECharts 对象包含 setOption 函数,其主要功能是设置一个 echart 实例的 option,并实现数据更新。基本上 echarts 的图表数据更新都是通过这个函数实现的
    • 这个函数有几个重构方法。其中参数的具体说明最好参考官方文档 setOption
    • 基本上参数使用来处理 option 的合并和懒加载的。
    • 具体的详细代码逻辑需要看源代码,并补全注释,同样清理 DEV 相关的调试代码
    
      setOption<Opt extends ECBasicOption>(option: Opt, notMerge?: boolean | SetOptionOpts, lazyUpdate?: boolean): void {
            // 禁止在主进程设置
            if (this[IN_MAIN_PROCESS_KEY]) {
                return;
            }
            // 当前的实例已经删除,不再设置配置
            if (this._disposed) {
                disposedWarning(this.id);
                return;
            }
            // 对配置项的合并进行处理
            let silent;
            let replaceMerge;
            let transitionOpt: SetOptionTransitionOpt;
            if (isObject(notMerge)) {
                lazyUpdate = notMerge.lazyUpdate;
                silent = notMerge.silent;
                replaceMerge = notMerge.replaceMerge;
                transitionOpt = notMerge.transition;
                notMerge = notMerge.notMerge;
            }
            // 设定主进程的标记,避免重复设置
            this[IN_MAIN_PROCESS_KEY] = true;
            // 记录当前的 model,这里 _model 实际就是 GlobalModel 记录了 option的大部分信息。他的另一个名称是 ecModel
            if (!this._model || notMerge) {
                const optionManager = new OptionManager(this._api);
                const theme = this._theme;
                const ecModel = this._model = new GlobalModel();
                ecModel.scheduler = this._scheduler;
                ecModel.ssr = this._ssr;
                ecModel.init(null, null, null, theme, this._locale, optionManager);
            }
            // 将实际的 option 添加到 _model之中,这里仅仅是保存 option,方便之后的组件使用
            this._model.setOption(option as ECBasicOption, { replaceMerge }, optionPreprocessorFuncs);
            
            // 建立更新的参数
            const updateParams = {
                seriesTransition: transitionOpt,
                optionChanged: true
            } as UpdateLifecycleParams;
            
            // 判定是否是懒更新
            if (lazyUpdate) {
                // 处理懒更新参数,跳过
                this[PENDING_UPDATE] = {
                    silent: silent,
                    updateParams: updateParams
                };
                this[IN_MAIN_PROCESS_KEY] = false;
    
                // `setOption(option, {lazyMode: true})` may be called when zrender has been slept.
                // It should wake it up to make sure zrender start to render at the next frame.
                this.getZr().wakeUp();
            }
            else {
                // 处理实时更新,会进一步调用渲染函数
                try {
                    // 用于处理渲染调度,跳过
                    prepare(this);
                    // 实际的更新函数,其中会调用 render 方法
                    updateMethods.update.call(this, null, updateParams);
                }
                catch (e) {
                    // 出现错误,退出主进程
                    this[PENDING_UPDATE] = null;
                    this[IN_MAIN_PROCESS_KEY] = false;
    
                    throw e;
                }
                
                // 处理非服务端渲染,保证 zrender 同步刷新
                // Ensure zr refresh sychronously, and then pixel in canvas can be
                // fetched after `setOption`.
                if (!this._ssr) {
                    // not use flush when using ssr mode.
                    this._zr.flush();
                }
                
                // 退出主进程
                this[PENDING_UPDATE] = null;
                this[IN_MAIN_PROCESS_KEY] = false;
                // 刷新action,跳过
                flushPendingActions.call(this, silent);
                triggerUpdatedEvent.call(this, silent);
            }
        }
    

    3.6 ECharts.internalField.updateMethods.update 函数逻辑

    • 这个函数主要是用来处理 echart 实例更新的。
    • 首先看参数
      • this 是当前实例 ECharts
      • payload 是事件的参数 Payload,第一次渲染是null,之后出现事件的时候会填充。
      • updateParams 是传入的更新参数 UpdateLifecycleParams
    • 之后看其内部逻辑,通过源代码查看
    update(this: ECharts, payload: Payload, updateParams: UpdateLifecycleParams): void {
      // 从 this 获取基本的数据模型ecModel、接口api、渲染器zr、坐标系统管理coordSysMgr、调度器 _scheduler
      const ecModel = this._model;
      const api = this._api;
      const zr = this._zr;
      const coordSysMgr = this._coordSysMgr;
      const scheduler = this._scheduler;
    
      // 处理异常,数据模型不存在,则退出
      // update before setOption
      if (!ecModel) {
          return;
      }
    
      // 设置数据模型的事件负载
      ecModel.setUpdatePayload(payload);
      // 处理调度器
      scheduler.restoreData(ecModel, payload);
      scheduler.performSeriesTasks(ecModel);
    
      // 建立坐标系统管理器
      // Create new coordinate system each update
      // In LineView may save the old coordinate system and use it to get the original point.
      coordSysMgr.create(ecModel, api);
       // 处理调度器的数据处理
      scheduler.performDataProcessorTasks(ecModel, payload);
      // 处理数据的stream
      // Current stream render is not supported in data process. So we can update
      // stream modes after data processing, where the filtered data is used to
      // determine whether to use progressive rendering.
      updateStreamModes(this, ecModel);
      // 更新坐标系统
      // We update stream modes before coordinate system updated, then the modes info
      // can be fetched when coord sys updating (consider the barGrid extent fix). But
      // the drawback is the full coord info can not be fetched. Fortunately this full
      // coord is not required in stream mode updater currently.
      coordSysMgr.update(ecModel, api);
      // 清空颜色盘
      clearColorPalette(ecModel);
      // 调度器处理可视化任务
      scheduler.performVisualTasks(ecModel, payload);
      // 执行渲染逻辑,关键
      render(this, ecModel, api, payload, updateParams);
    
      // 设置背景色
      // Set background
      const backgroundColor = ecModel.get('backgroundColor') || 'transparent';
      // 设置主题模式 
      const darkMode = ecModel.get('darkMode');
      // 设置渲染器背景色
      zr.setBackgroundColor(backgroundColor);
    
      // 强制渲染器暗色模式
      // Force set dark mode.
      if (darkMode != null && darkMode !== 'auto') {
          zr.setDarkMode(darkMode);
      }
      // 触发 afterupdate 事件
      lifecycle.trigger('afterupdate', ecModel, api);
    },
    
    • 这个 update 函数最关键的部分就是 render 函数的调用。其中调用了全部的关键参数。目前最初 option 相关的数据都存储于 ecModel 之中了。

    3.7 查看 ECharts.internalField.render 函数

    • 这个函数组合了多个函数的功能,依次实现了组件渲染,和视图渲染。
    • 具体查看其中的代码
    render = (
      ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload,
      updateParams: UpdateLifecycleParams
    ) => {
      // 处理 zlevel 属性,前后的覆盖关系
      allocateZlevels(ecModel);
    
      // 渲染组件
      renderComponents(ecIns, ecModel, api, payload, updateParams);
      
      // 渲染视图,标记全部视图为不活动
      each(ecIns._chartsViews, function (chart: ChartView) {
          chart.__alive = false;
      });
      // 对视图系列进行渲染
      renderSeries(ecIns, ecModel, api, payload, updateParams);
      
      // 删除没有渲染的视图
      // Remove groups of unrendered charts
      each(ecIns._chartsViews, function (chart: ChartView) {
          if (!chart.__alive) {
              chart.remove(ecModel, api);
          }
      });
    };
    
    • 进一步的渲染方法就是 renderComponents 和 renderSeries,其中 renderComponent 比较简单
    • 其中多了一个参数是 dirtyList 使处理脏的组件列表。
    renderComponents = (
      ecIns: ECharts, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload,
      updateParams: UpdateLifecycleParams, dirtyList?: ComponentView[]
    ) => {
      // 遍历实例中注册的组件和脏列表之中的组件
      each(dirtyList || ecIns._componentsViews, function (componentView: ComponentView) {
          // 获得组件的模型
          const componentModel = componentView.__model;
          // 清理组件状态
          clearStates(componentModel, componentView);
          // 调用组件视图的渲染函数,关键
          componentView.render(componentModel, ecModel, api, payload);
          // 更新z高度
          updateZ(componentModel, componentView);
          // 更新状态
          updateStates(componentModel, componentView);
      });
    };
    
    • 至此就清楚关于组件渲染的流程了。如果需要定制组件,那么就要实现 ComponentView 的 render 函数就可以了。
    • 对于 ChartView 主要是针对 Series 的渲染。所以如果是自定义坐标轴相关的组件,只用 ComponentView 就够了。

    4. 自定义 ComponentView

    4.1 目标

    • 仿照 dataZoom 组件定义一个的x坐标轴的组件
    • 使用矩形绘制整个坐标轴范围内容
    • 对于现有 x 坐标轴仅仅隐藏。
    • 新的坐标轴可以跟随 x 轴的移动更新。

    4.2 渲染需求

    • 坐标轴组件需要定位在原来的x轴坐标的位置
    • 使用矩形包裹全部 x 轴
    • splitline使用直线绘制
    • tickLabel在中间显示
    • 隐藏现有 x 轴

    4.3 组件创建

    4.3.1 MyAxisModel

    • MyAxisModel 是组件的数据模型,继承自ComponentModel
    • 模型之中可以附加 MyAxisOption,作为附加数据选项,方便在之后的配置项中进行设置
    • 基本代码如下
    import * as echarts from "echarts/core";
    
    // 数据模型选项的接口,可以在之后的配置项中和其他选项合并
    // 实际应当继承 ComponentOption,但是此类并没有对外开放
    export interface MyAxisOption {
      // 配置项的名称,自定义的组件这个名称最好相同。否则可能找不到组件
      // 在 echarts 的配置项中需要使用此名称来获得对应组件数据模型和视图
      mainType?: "myAxis";
    }
    
    // 实际的数据模型
    class MyAxisModel extends echarts.ComponentModel<MyAxisOption> {
      // 数据模型的类型,自定义组件此名称最好相同
      static type = "myAxis";
      type = MyAxisModel.type;
    }
    
    export default MyAxisModel;
    
    • 上面的数据模型并没有添加任何的选项,但是作为与 echarts 的配置项的接口,这个数据模型是必须的。否则找不到对应的视图。

    4.3.2 MyAxisView

    • MyAxisView 需要继承自 ComponentView
    • 实现自定义可以主要通过重载 render 函数实现
    • 内部的具体逻辑参考代码即可
    import * as echarts from "echarts/core";
    import MyAxisModel from "./myAxisModel";
    
    // 继承自 ComponentView
    class MyAxisView extends echarts.ComponentView {
      static type = "myAxis";
      type = MyAxisView.type;
    
      // 重载渲染函数,
      // _model 就是组件的数据模型
      // ecModel 就是 GlobalModel, 通过这个模型可以获得其他组件信息
      // _api 是获取 dom 相关信息的接口
      // _payload 是事件相关数据
      render(_model: MyAxisModel, ecModel: any, _api: any, _payload: any) {
        // ComponentView 类的图形容器组,全部的 zrender 绘制的内容都是要添加到这个 group 之中
        const group = this.group;
        // 获得第一个x坐标轴
        const xAxisModel = ecModel.getComponent("xAxis", 0);
        // 获得x坐标轴的坐标系统
        const axisCoordSysModel = xAxisModel.getCoordSysModel();
        const coorSys = axisCoordSysModel.coordinateSystem;
        // 获得x坐标轴的显示范围
        const axisRect = coorSys.getRect();
        // 绘制包裹x坐标轴的矩形,这里使用的是利用 zrender 封装的图形接口
        const rect = new echarts.graphic.Rect({
          shape: {
            x: axisRect.x,
            y: axisRect.y + axisRect.height,
            width: axisRect.width,
            height: 30,
          },
          style: {
            fill: "none",
            stroke: "#0F0",
          },
        });
        // 添加矩形到 group,完成渲染
        group.add(rect);
      }
    }
    
    export default MyAxisView;
    
    • 自定义组件基本上可以获得 echarts 其他组件的全部信息
    • 上面的代码中就是获得了第一个 x 轴的坐标信息,从而绘制了包裹 x 轴的绿色矩形框
    • 参考 dataZoom 组件可以获取更多的 echarts 图表的其他组件的信息
    • 其中获取其他组件的方法主要是以下的函数,获取的都是组件数据模型:
      • GlobalModel.getComponent,通常在 view 之中,通过 ecModel 调用
      • ComponentModel.getReferringComponents,通常在 model 之中调用

    4.3.3 install

    • 设置完成 view 和 model,就需要添加一个 install 文件
    • 这个文件主要是用来注册组件的视图和模型的,在实际使用中引入的组件文件也是这个install文件。
    • 之后 echarts 会使用 use 函数来调用其中的 intall 函数完成注册
    • 没有注册的组件即使出现在配置项中,也是无法识别的。
    import MyAxisModel from "./myAxisModel";
    import MyAxisView from "./myAxisView";
    
    // intall 函数实际就是注册函数
    // 当 echart 使用 use 函数的时候会调用 install 函数,完成注册
    // 只有注册的组件才能通过配置项配置,并渲染
    export default function install(registers: any) {
      // 注册组件模型
      registers.registerComponentModel(MyAxisModel);
      // 注册组件视图
      registers.registerComponentView(MyAxisView);
    }
    

    4.4 使用组件

    • 具体使用组件实际就与echarts的其他组件的使用方式类似了。
    • 首先引入组件的install函数,通过 use 注册完成
    • 之后在配置项中写上组件的type对应的名称,如果有配置,在对应的配置中添加即可。
    • 修改之后的 echarts 示例如下
    import "../style.css";
    import * as echarts from "echarts/core";
    import {
      BarChart,
      // 系列类型的定义后缀都为 SeriesOption
      BarSeriesOption,
    } from "echarts/charts";
    import {
      TitleComponent,
      // 组件类型的定义后缀都为 ComponentOption
      TitleComponentOption,
      TooltipComponent,
      TooltipComponentOption,
      GridComponent,
      GridComponentOption,
      // 数据集组件
      DatasetComponent,
      DatasetComponentOption,
    } from "echarts/components";
    import { CanvasRenderer } from "echarts/renderers";
    // 自定义组件
    import MyAxis from "../myAxis/install";
    
    // 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
    type ECOption = echarts.ComposeOption<
      | BarSeriesOption
      | TitleComponentOption
      | TooltipComponentOption
      | GridComponentOption
      | DatasetComponentOption
    >;
    
    // 注册必须的组件
    echarts.use([
      TitleComponent,
      TooltipComponent,
      GridComponent,
      DatasetComponent,
      BarChart,
      CanvasRenderer,
      MyAxis, // 注册自定义组件
    ]);
    
    // 绘制图表
    const option: ECOption = {
      title: {
        text: "ECharts 入门示例",
      },
      tooltip: {},
      xAxis: {
        data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
      },
      yAxis: {},
      series: [
        {
          name: "销量",
          type: "bar",
          data: [5, 20, 36, 10, 10, 20],
        },
      ],
     // 添加自定义组件配置项
      myAxis: {},
    };
    // 基于准备好的dom,初始化echarts实例
    const elem = document.getElementById("main");
    if (elem) {
      const myChart = echarts.init(elem);
      // 绘制图表
      myChart.setOption(option);
    }
    
    • 最终效果如下图,可以看到 x 轴已经被绿色的矩形框包裹了


      image.png

    4.5 接下来的目标

    • 完成了自定义组件的基本框架,就可以在之上增加功能了。
    • 同样需要参考现有组件的定义,才能更好的制作自己的组件

    相关文章

      网友评论

          本文标题:echarts 图表定制 02

          本文链接:https://www.haomeiwen.com/subject/tumxjdtx.html