Skip to content

Latest commit

 

History

History
300 lines (223 loc) · 13.2 KB

File metadata and controls

300 lines (223 loc) · 13.2 KB
title 11.2 Source Code Interpretation of Theme Updates
key words VisActor,VChart,VTable,VStrory,VMind,VGrammar,VRender,Visualization,Chart,Data,Table,Graph,Gis,LLM

Basic Concepts of Theme Updates

VChart theme switching is a common operation: for example, according to different seasons, holidays, or internationalization, personalized color schemes, and the common night mode. Users can manually or listen to the user's system to switch different styles of themes to adapt to different usage environments.

Theme Update Examples

For example, registration and switching of night mode:

VChart.ThemeManager.registerTheme('darkTheme', { ... });
VChart.ThemeManager.registerTheme('lightTheme', { ... });

function toggleTheme(isDarkMode) {
  const themeName = isDarkMode ? 'darkTheme' : 'lightTheme';
  VChart.ThemeManager.setCurrentTheme(themeName);
}    

Style switching for different application scenarios:

// 不同风格的主题配置
const themes = {
  'finance': { ... },
  'medical': { ... },
  'technology': { ... }
};

Object.keys(themes).forEach(key => {
  VChart.ThemeManager.registerTheme(key + 'Theme', themes[key]);
});

function switchDashboardTheme(businessType) {
  const themeName = businessType + 'Theme';
  VChart.ThemeManager.setCurrentTheme(themeName);
}    

Source Code Location and Content Related to Themes

  • package/vchart/scr/core/vchart.ts: The theme updater for a single chart instance, implementing the specific theme application logic, transforming the global theme into actual style changes for the chart. The main update logic of the chart is here.

  • package/vchart/src/core/instance-manager.ts: The central hub for chart instance registration and management, providing the infrastructure for traversing and locating theme instance updates, ensuring that each chart can receive theme updates.

  • package/vchart/src/theme/theme-manager.ts: The global theme scheduling center, responsible for theme registration, retrieval, and global updates, providing a unified theme management entry and coordination mechanism.

Through the methods defined in the `VChart` class, the core rendering and update logic of the chart is implemented. `ThemeManager` and `InstanceManager` are responsible for the global management of themes and instances, respectively, forming a decoupled, flexible, and extensible chart library architecture; `VChart` provides a unified update entry, implementing most of the update operation logic; while `ThemeManager` and `InstanceManager` achieve global theme updates through instance registration and traversal mechanisms.
# In-depth Analysis of the Theme Update Process

The VChart official website divides theme updates into two dimensions, namely

  • Updating the theme of a single chart instance

  • Updating the theme of all charts globally through ThemeManager.

The specific approach can be viewed at 🎁 VisActor Data Visualization Competition. Both methods use the same setCurrentTheme call to switch themes. The former is called by an instance generated by the VChart object, updating a single chart; the latter is called through ThemeManager, updating the global chart theme. Therefore, my approach to reading the source code is based on the declaration and definition of the setCurrentTheme method, delving deeper layer by layer.

Example:

Updating a single instance:

const vchart = new VChart(spec, { dom: CONTAINER_ID });
//单个theme实例的更新
vchart.setCurrentTheme('userTheme');    

Update of global themes:

// 注册主题
VChart.ThemeManager.registerTheme('userTheme', theme);
//全局主题更新
VChart.ThemeManager.setCurrentTheme('userTheme');    

Theme Update Executor: VChart.ts \r\n\r\nAnalyze update behavior, focusing on reading this call chain: \r\n\r\n\u0060setCurrentTheme()\u0060→ \u0060setCurrentThemeSync()\u0060\u0026\u0060updateCustomConfigAndRerender()\u0060→ \u0060_setCurrentTheme()\u0060 execution process \r\n\r\n### \u0060_setCurrentTheme() \u0060\r\n\r

 protected _setCurrentTheme(name?: string): IUpdateSpecResult {
    this._updateCurrentTheme(name);
    this._initChartSpec(this._getSpecFromOriginalSpec(), 'setCurrentTheme');
    this._chart?.setCurrentTheme();
    return { change: true, reMake: false };
  }    

First, analyze the internal private method _setCurrentTheme, first trigger _updateCurrentTheme, enter the theme merging and parsing process explained in section 11-1, then reinitialize the chart specification (spec). chart is the core rendering instance of the chart, responsible for specific rendering and interaction logic. Here, the method setCurrentTheme is called, which will be analyzed in detail below.

Finally, the returned { change: true, reMake: false } indicates: change means the configuration has changed, triggering a re-render, informing the rendering engine that an update is needed. reMake means a complete rebuild of the chart is not necessary, only a partial update is required. This structure is returned to trigger the chart update behavior in the subsequent setCurrentThemeSync in updateCustomConfigAndRerender.

setCurrentThemeSync() & updateCustomConfigAndRerender()

 /**
   * **同步方法** 设置当前主题。
   * **注意,如果在 spec 上配置了 theme,则 spec 上的 theme 优先级更高。**
   * @param name 主题名称
   * @returns
   */
  setCurrentThemeSync(name: string) {
    if (!ThemeManager.themeExist(name)) {
      return this as unknown as IVChart;
    }
    const result = this._setCurrentTheme(name);
    this._setFontFamilyTheme(this._currentTheme?.fontFamily as string);
    this.updateCustomConfigAndRerender(result, true, {
      transformSpec: false,
      actionSource: 'setCurrentTheme'
    });
    return this as unknown as IVChart;
  }    

After checking for null, we first get the agreed object { change: true, reMake: false }, which means the theme is updated and must trigger a re-render, but there is no need to completely rebuild the table, just a partial update is sufficient.

updateCustomConfigAndRerender()

   //result: { change: true, reMake: false };
   
   //调用updateCustomConfigAndRerender
   this.updateCustomConfigAndRerender(result, true, {
      transformSpec: false,
      actionSource: 'setCurrentTheme'
   });
    
  //updateCustomConfigAndRerender具体实现
  updateCustomConfigAndRerender(
    updateSpecResult: IUpdateSpecResult | (() => IUpdateSpecResult),
    sync?: boolean,
    option: IVChartRenderOption = {}
  ) {
    if (this._isReleased || !updateSpecResult) {
      return undefined;
    }
    if (isFunction(updateSpecResult)) {
      updateSpecResult = updateSpecResult();
    }

    if (updateSpecResult.reAnimate) {
      this.stopAnimation();
      this._updateAnimateState(true);
    }

    this._reCompile(updateSpecResult);
    if (sync) {
      return this._renderSync(option);
    }
    return this._renderAsync(option);
  }
    

updateCustomConfigAndRerender 是主题重渲染的核心逻辑,也是任何主题配置更改(数据模型、图表spec等发生更改时)重渲染的核心。在主题更新里的逻辑并不复杂,因为传入的updateSpecResult:{ change: true, reMake: false } 并不包括动画处理、也不是函数类型,只执行了_reCompile()_renderSync();

recompile()
  protected _reCompile(updateResult: IUpdateSpecResult, morphConfig?: IMorphConfig) {
    if (updateResult.reMake) {
      this._releaseData();
      this._initDataSet();
      this._chart?.release();
      this._chart = null as unknown as IChart;
    }

    if (updateResult.reTransformSpec) {
      // 释放图表等等
      this._chartSpecTransformer = null;
    }

    // 卸载了chart之后再设置主题 避免多余的reInit
    if (updateResult.changeTheme) {
      this._setCurrentTheme();
      this._setFontFamilyTheme(this._currentTheme?.fontFamily as string);
    } else if (updateResult.changeBackground) {
      this._compiler?.setBackground(this._getBackground());
    }

    if (updateResult.reMake) {
      // 如果不需要动画,那么释放item,避免元素残留
      this._compiler?.releaseGrammar(this._option?.animation === false || this._spec?.animation === false);
      // chart 内部事件 模块自己必须删除
      // 内部模块删除事件时,调用了event Dispatcher.release() 导致用户事件被一起删除
      // 外部事件现在需要重新添加
      this._userEvents.forEach(e => this._event?.on(e.eType as any, e.query as any, e.handler as any));

      if (updateResult.reSize) {
        this._doResize();
      }
    } else {
      if (updateResult.reCompile) {
        // recompile
        // 清除之前的所有 compile 内容
        this._compiler?.clear(
          { chart: this._chart, vChart: this },
          this._option?.animation === false || this._spec?.animation === false
        );
        // TODO: 释放事件? vgrammar 的 view 应该不需要释放,响应的stage也没有释放,所以事件可以不绑定
        // 重新绑定事件
        // TODO: 释放XX?
        // 重新compile
        this._compiler?.compile({ chart: this._chart, vChart: this }, {});
      }
      if (updateResult.reSize) {
        const { width, height } = this.getCurrentSize();
        this._chart.onResize(width, height, false);
        this._compiler.resize(width, height, false);
      }
    }
  }    
  • When reMake is true, the chart will be completely reset through releaseData, initDataSet, and release, releasing all related resources to prepare for re-rendering. As mentioned earlier, theme updates do not completely reset the chart.

  • When reMake is false, re-compilation and chart resizing will be performed based on the values of reCompile and reSize, respectively. Operations are implemented through methods on instances such as _chart and _compiler.

After reading the source code, it is known that theme updates do not trigger reCompile operations. Generally, reCompile is needed only when there are additions or deletions in the graphics.

_renderSync()
  protected _renderSync = (option: IVChartRenderOption = {}) => {
    const self = this as unknown as IVChart;
    if (!this._beforeRender(option)) {
      return self;
    }
    // 填充数据绘图
    this._compiler?.render(option.morphConfig);
    this._afterRender();
    return self;
  };
    

This is a synchronous rendering method, which prepares and checks before rendering through _beforeRender to ensure that the rendering conditions are met; it calls the render method of _compiler to perform the actual chart drawing, and can pass in transformation configurations; after completing the drawing, _afterRender performs post-rendering cleanup and state updates, and returns the current instance.

Principle of Global Update

Theme Scheduling Center theme-manager

As mentioned earlier, the VChart instance updates the theme for a single chart, while the themeManager updates the global theme

https://www.visactor.io/vchart/guide/tutorial_docs/Theme/Customize_Theme After registering the theme in `ThemeManager`, you can use `ThemeManager.setCurrentTheme` to hot-update the registered theme by theme name. Note: This method will affect all chart instances on the page.
```xml static setCurrentTheme(name: string) { if (!ThemeManager.themeExist(name)) { return; } ThemeManager._currentThemeName = name; InstanceManager.forEach((instance: IVChart) => instance?.setCurrentTheme(name)); }
It is not difficult to see that this method globally sets the current theme name, then iterates over all registered chart instances and calls `setCurrentTheme` on each instance, thereby achieving a global update of the theme for all instances.    

### Reason for Theme Instance Operations instance-manager

The operation of the instance on the chart is actually because, within the constructor of the VChart class, the current VChart instance is registered in `InstanceManager.instances`, thereby supporting global operations such as unified theme updates.    

```xml
  export class VChart implements IVChart {
      constructor(spec: ISpec, options: IInitOption) {
          //......其他
          InstanceManager.registerInstance(this);
      }
 }    

Conclusion

In summary, most of the update operations in vchart.ts are implemented in the VChart class, not only involving theme updates but also other situations that require updates. Theme updates are just a part of it; the theme-manager and instance-manager allow developers to manage global theme updates through the registration and traversal of instances, achieving both single instance updates and global updates of themes.


This document is provided by

Dun Dun (https://github.com/Shabi-x)

This document is revised and organized by

Xuan Hun