!!!###!!!title=Global Morphing Animation——VisActor/VChart Contributing Documents!!!###!!!!!!###!!!description=---title: 10.2 Global Morphing Animationkey words: VisActor,VChart,VTable,VStrory,VMind,VGrammar,VRender,Visualization,Chart,Data,Table,Graph,Gis,LLM---!!!###!!!

10.2 Global Morphing Animation

Score: 8

  • Global Animation:

  • Code Entry: packages/vchart/src/animation/

  • Key Points:

  • Implementation of Global Animation

  • Other Reference Documents:

https://www.visactor.io/vchart/guide/tutorial_docs/Animation/Animation_Types

https://www.visactor.io/vrender/guide/asd/Basic_Tutorial/Animate

https://visactor.io/vgrammar/guide/guides/animation

Magic Frame (Part 1): The Principle of Animation Implementation in Front-end Chart Libraries - A vivid visualization work often involves animation. Whether it's various charts or narrative works, organizing the week - Juejin

In 10.1, we initially learned about the design of the animation system in VChart and the creation examples of charts. This section continues to introduce the transition design when switching between different chart configurations in VChart.

Definition

VChart provides morphing animations for switching between related series, which we call global morphing animations.

When updating the chart configuration through updateSpec, VChart will detect whether the two related series of the old and new charts meet the conditions for morphing animation, thereby executing dynamic transitions between one-to-one, one-to-many, or many-to-one graphics. Global morphing animations allow users to have a better visual experience when the type of chart being displayed changes, avoiding the feeling of instantaneous change. After all, visual comfort is an important factor we should focus on in the process of displaying and analyzing data.

https://visactor.com/vchart/api/API/vchart Reference API Documentation

updateSpec
异步spec 更新,会自动渲染图表不需要再调用 renderAsync() 等渲染方法。
/**
 * spec 更新
 * @param spec
 * @param forceMerge 是否强制合并,默认为 false
 * @param morphConfig morph 动画配置
 * @returns
 */
updateSpec: (spec: ISpec, forceMerge?: boolean, morphConfig?: IMorphConfig) => Promise<IVChart>;

Example of Effects

Below are two example configurations to illustrate the effects of this type of transition animation:

One-to-One Animation

One-to-one animation refers to the transition animation between two different graphics. For example, in the example below, it shows the global animation when switching between a pie chart and a bar chart:

/**
 * 自1.12.0后,全局形变动画需要手动注册才能生效
 *
 * import { registerMorph } from '@visactor/vchart';
 *
 * registerMorph();
 *
 * 自2.0.0开始,全局形变动画默认开启,不再需要手动注册
 */

VCHART_MODULE.registerMorph && VCHART_MODULE.registerMorph();

const pieSpec = {
  type: 'pie',
  data: [
    {
      values: [
        { type: '1', value: Math.random() },
        { type: '2', value: Math.random() },
        { type: '3', value: Math.random() }
      ]
    }
  ],
  outerRadius: 0.8,
  innerRadius: 0.6,
  valueField: 'value',
  categoryField: 'type',
  tooltip: false
};

const barSpec = Object.assign({}, pieSpec, {
  type: 'bar',
  xField: 'type',
  yField: 'value',
  seriesField: 'type'
});

const specs = [pieSpec, barSpec];

const vchart = new VChart(specs[0], { dom: CONTAINER_ID });

vchart.renderSync();
let count = 1;
setInterval(() => {
  vchart.updateSpec(specs[count % 2]);
  count++;
}, 2000);


One-to-Many Animation

One-to-many animation refers to the transition of a single graphic element into multiple graphic elements. For example, in the example below, a global animation is shown when switching between a bar chart and a scatter plot, where the animation of splitting a large bar into multiple scatter points is a one-to-many animation.

/**
 * 自1.12.0后,全局形变动画需要手动注册才能生效
 *
 * import { registerMorph } from '@visactor/vchart';
 *
 * registerMorph();
 *
 * 自2.0.0开始,全局形变动画默认开启,不再需要手动注册
 */

VCHART_MODULE.registerMorph && VCHART_MODULE.registerMorph();

function calculateAverage(data, dim) {
  let total = 0;
  for (let i = 0; i < data.length; i++) {
    total += data[i][dim];
  }
  return (total /= data.length);
}

function generateData(type) {
  const data = [];
  for (let i = 0; i < 10; i++) {
    data.push({ x: i, y: Math.random(), type });
  }
  return data;
}
const DataA = generateData('A');

const DataB = generateData('B');

const barSpec = {
  type: 'common',
  series: [
    {
      type: 'bar',
      data: { values: [{ value: calculateAverage(DataA, 'y'), type: 'A' }] },
      xField: 'type',
      yField: 'value',
      morph: {
        morphKey: 'A'
      }
    },
    {
      type: 'bar',
      data: { values: [{ value: calculateAverage(DataB, 'y'), type: 'B' }] },
      xField: 'type',
      yField: 'value',
      morph: {
        morphKey: 'B'
      }
    }
  ],
  axes: [
    { orient: 'left', type: 'linear', max: 1 },
    { orient: 'bottom', type: 'band' }
  ]
};

const scatterSpec = {
  type: 'common',
  series: [
    {
      type: 'scatter',
      data: { values: DataA },
      xField: 'x',
      yField: 'y',
      seriesField: 'type',
      morph: {
        morphKey: 'A',
        morphElementKey: 'type'
      }
    },
    {
      type: 'scatter',
      data: { values: DataB },
      xField: 'x',
      yField: 'y',
      seriesField: 'type',
      morph: {
        morphKey: 'B',
        morphElementKey: 'type'
      }
    }
  ],
  axes: [
    { orient: 'left', type: 'linear', zero: false, max: 1 },
    { orient: 'bottom', type: 'band' }
  ]
};

const specs = [barSpec, scatterSpec];

const vchart = new VChart(specs[0], { dom: CONTAINER_ID });

vchart.renderSync();
let count = 1;
setInterval(() => {
  vchart.updateSpec(specs[count % 2]);
  count++;
}, 3000);

Many-to-One Animation

Many-to-one animation refers to multiple graphic elements transitioning into one element. For example, in the example above, we can have multiple points of a scatter series merge into one large column.

Interpretation of the Source Code Execution Process for Effect Implementation

The transition between different charts can be explained by updating the configuration, and with morphing animation enabled, the transition effects of series elements are automatically recognized. Below is an explanation of the default effect settings.

Draft Interpretation of Global Animation Implementation

Global animations refer to those animation effects that apply at the entire chart level. They can be applied to the overall entrance animation when the chart loads, the unified change animation when data updates, and the overall exit animation before the chart is destroyed. In VChart, the design and implementation of global animations rely on several core components and mechanisms, including the Factory class, AnimateManager class, IAnimationSpec interface, etc.

1. Animation Registration and Management

Factory Class

The Factory class is a key player in the animation system, responsible for managing and registering various types of animations. Through the static method registerAnimation, we can associate specific animation logic with a name for subsequent use.

class Factory {
  static registerAnimation(key: string, animation: (params?: any, preset?: any) => MarkAnimationSpec) {
    Factory._animations[key] = animation;
  }
}

When you need to add animation to a chart element, you can use Factory.getAnimationInKey to obtain a registered animation and apply it to the corresponding graphic or graphical element.

2. Animation Configuration Structure

IAnimationSpec Interface

The IAnimationSpec interface defines the basic structure of animation configuration, covering various states from entry (animationAppear) to exit (animationDisappear). Each state can accept a boolean value (enable/disable), a preset configuration object, or a custom configuration object as a parameter.

interface IAnimationSpec<MarkName extends string, Preset extends string> {
  animationAppear?: boolean | IStateAnimateSpec<Preset> | IMarkAnimateSpec<MarkName>;
  animationEnter?: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>;
  animationUpdate?: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>;
  animationExit?: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>;
  animationDisappear?: boolean | ICommonStateAnimateSpec | IMarkAnimateSpec<MarkName>;
  animationState?: boolean | IStateAnimationConfig;
  animationNormal?: IMarkAnimateSpec<MarkName>;
}

These configuration options allow developers to flexibly control the behavior of animations in different states, such as setting duration, easing functions, animation types, etc.

3. Animation State Management

AnimateManager Class

AnimateManager inherits from StateManager and implements the IAnimate interface, used to manage the state of animations. It provides methods to update animation states and trigger corresponding animation logic based on the current state.

class AnimateManager extends StateManager implements IAnimate {
  updateAnimateState(state: AnimationStateEnum, noRender?: boolean) {
    if (state === AnimationStateEnum.update) {
      // 更新状态下的动画逻辑
    } else if (state === AnimationStateEnum.appear) {
      // 出现状态下的动画逻辑
    } else {
      // 其他状态下的动画逻辑
    }
  }
}

In addition, AnimateManager is also responsible for generating unique identifiers (IDs) and signal names to ensure that each animation instance can be correctly identified and managed.

4. Animation Configuration Generation

animationConfig Function

To simplify the merging process between user configurations and default configurations, VChart provides a helper function called animationConfig. This function iterates through all possible animation states and constructs the final animation configuration object based on the user-provided configuration or the default configuration.

function animationConfig<Preset extends string>(
  defaultConfig: MarkAnimationSpec = {},
  userConfig?: Partial<Record<IAnimationState, boolean | IStateAnimateSpec<Preset> | IAnimationConfig | IAnimationConfig[]>>,
  params?: { dataIndex: (datum: any, params: any) => number; dataCount: () => number; }
): MarkAnimationSpec {
  const config = {} as MarkAnimationSpec;

  for (let i = 0; i < AnimationStates.length; i++) {
    const state = AnimationStates[i];
    const userStateConfig = userConfig ? userConfig[state] : undefined;

    if (userStateConfig === false) continue;

    if (state === 'normal') {
      userStateConfig && (config.normal = userStateConfig as IAnimationTypeConfig);
      continue;
    }

    let defaultStateConfig: IAnimationConfig[];
    if (isArray(defaultConfig[state])) {
      defaultStateConfig = defaultConfig[state] as IAnimationConfig[];
    } else {
      defaultStateConfig = [{ ...DEFAULT_ANIMATION_CONFIG[state], ...defaultConfig[state] } as any];
    }

    config[state] = defaultStateConfig;
  }

  return config;
}

This function handles the merging of default configurations and user configurations, considering that certain states (such as normal) can directly use the user-provided configuration without additional processing.

5. Specific Implementation of Global Animation

Registration of Global Animation

Taking line charts or area charts as an example, the registerVGrammarLineOrAreaAnimation function demonstrates how to batch register a series of animation methods. These animations cover effects such as point growth, point movement, and clipping, and are applicable to both the X-axis and Y-axis directions.

const registerVGrammarLineOrAreaAnimation = () => {
  View.useRegisters([
    registerGrowPointsInAnimation,
    registerGrowPointsOutAnimation,
    registerGrowPointsXInAnimation,
    registerGrowPointsXOutAnimation,
    registerGrowPointsYInAnimation,
    registerGrowPointsYOutAnimation,
    registerClipInAnimation,
    registerClipOutAnimation
  ]);
};

Initialization of Global Animation

In the implementation files of specific series (such as bar charts, pie charts, etc.), the initAnimation method is usually called during the initialization phase to set up animation configurations. This method combines user-provided configurations with default configurations to generate the final animation configuration and applies it to the corresponding graphic elements or shapes.

initAnimation(): void {
  const animationParams = getGroupAnimationParams(this);
  const appearPreset = (this._spec?.animationAppear as IStateAnimateSpec<ScatterAppearPreset>)?.preset;
  this._symbolMark.setAnimationConfig(
    animationConfig(
      Factory.getAnimationInKey('scatter')?.({}, appearPreset),
      userAnimationConfig(SeriesMarkNameEnum.point, this._spec, this._markAttributeContext),
      animationParams
    )
  );
}

Here, the animationConfig function is used to merge default configurations and user configurations, while userAnimationConfig is responsible for extracting the animation configuration information provided by the user. Finally, the generated configuration is applied to specific graphic elements through the setAnimationConfig method.

6. Execution of Animation Tasks

IAnimationTask Interface

For complex animation sequences, VChart introduces the IAnimationTask interface to describe the data structure of animation tasks. Each task includes time offsets, action queues, and successor task lists, forming a chain-like animation execution mechanism.

interface IAnimationTask {
  timeOffset: number;
  actionList: Action[];
  nextTaskList: IAnimationTask[];
}

This design allows multiple animation tasks to be executed sequentially or concurrently, enabling more complex and delicate animation effects.

7. Example: Creating a Global Entrance Animation

Suppose we want to add a global fade-in entrance animation to a newly created bar chart. Here are the detailed implementation steps:

  • Define Animation Configuration: First, specify animationAppear as true in the chart configuration to enable the entrance animation. Additionally, you can further customize the specific behavior of the animation, such as choosing a fade-in effect, setting the duration, and easing function.
const chartSpec = {
  // ... 其他配置 ...
  animationAppear: {
    type: 'fadeIn',
    duration: 1000,
    easing: 'easeInOutQuad'
  },
  series: [
    {
      type: 'bar',
      data: [/* 数据数组 */]
    }
  ]
};

  • Register Fade-in Animation: Next, we need to ensure that the fade-in animation has been correctly registered in the system. This step is usually completed at project startup or explicitly called where needed.
import { Factory } from '@visactor/vchart';
import { Appear_FadeIn } from './series/bar/animation';

Factory.registerAnimation('fadeIn', Appear_FadeIn);

  • Initialize the chart instance: With the above configuration, we can initialize a VChart instance and pass the configuration to it. This will trigger the chart rendering process and apply the corresponding animation effects.
import { VChart } from '@visactor/vchart';

const container = document.getElementById('chart-container');
const chart = new VChart({
  el: container,
  spec: chartSpec,
  options: {
    animation: true, // 开启动画
    theme: 'light'   // 使用浅色主题
  }
});

  • Trigger Animation: Once the chart is rendered, any changes in data will automatically trigger animations. For example, when the page first loads, all bars will gradually appear with a fade-in effect; when new data is added, new bars will enter in the same way.

  • Manual Control of Animation: If you need more precise control over the animation, such as pausing or resuming it, you can use the relevant methods provided by the VChart instance.

// 暂停所有正在进行的动画
chart.pauseAnimation();

// 恢复之前暂停的动画
chart.resumeAnimation();

Summary

Through the above steps, we have detailed the implementation principles of global animation in VChart. The animation system of VChart cleverly combines the factory pattern, state manager pattern, and modular animation configuration, providing not only a rich set of built-in animation effects but also supporting highly customizable needs. Developers can flexibly configure and combine different animations according to actual application scenarios to create visual effects that are both beautiful and practical.

This document was revised and organized by the following personnel

玄魂