!!!###!!!title=Animation Orchestration——VisActor/VChart Contributing Documents!!!###!!!!!!###!!!description=---title: 10.5 Animation Arrangement key words: VisActor,VChart,VTable,VStrory,VMind,VGrammar,VRender,Visualization,Chart,Data,Table,Graph,Gis,LLM---!!!###!!!

10.5 Animation Arrangement

Score: 4

  • Global Animation:

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

  • Key Points Interpretation:

  • Implementation of Animation Arrangement

  • 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 Front-end Chart Library Animation Implementation A vivid visualization work often involves animation. Whether it's various charts or narrative works, organizing the week - Juejin

The animation arrangement in VChart's source code mainly revolves around generating and configuring animations to achieve animation effects in different states. Below, we interpret its implementation from several key functions and type definitions:

  • Type Definitions and Constants

utils.ts

Apply

  • // Import various types and constants import type { IAnimationConfig } from '@visactor/vgrammar-core'; // ... other imports ... /** Define an array of animation states, including all keys in the default animation configuration and 'normal' */ export const AnimationStates = [...Object.keys(DEFAULT_ANIMATION_CONFIG), 'normal'];

  • Type Imports: Various types are imported from different modules, such as IAnimationConfig, IElement, etc., which are used to define animation configurations, elements, etc., ensuring type safety in the code.

  • AnimationStates Constant: Contains all possible animation states, including states in the default animation configuration and the 'normal' state, used for subsequent traversal and processing of animation configurations in different states.

  • Generate Animation Configuration

utils.ts

export 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;
  }
) {
  // ... 函数实现 ...
}    

  • Parameters:

  • defaultConfig: Default animation configuration.

  • userConfig: User-defined animation configuration, possibly for partial states.

  • params: Parameters containing data index and data count functions.

  • Implementation logic:

  • Create an empty object config to store the final animation configuration.

  • Iterate over the AnimationStates array to process the configuration of each animation state.

  • Merge or override the animation configuration of the corresponding state based on user configuration and default configuration.

  • For the exit state, set the control option stopWhenStateChange: true.

  • Handle the oneByOne option in the user configuration to generate an animation configuration that executes one by one.

  • Return the final animation configuration.

  • Generate user animation configuration

utils.ts

export function userAnimationConfig<M extends string, Preset extends string>(
  markName: SeriesMarkNameEnum | string,
  spec: IAnimationSpec<M, Preset>,
  ctx: IModelMarkAttributeContext
) {
  // ... 函数实现 ...
}    

  • Parameters:

  • markName: Mark name.

  • spec: Animation specification.

  • ctx: Model mark attribute context.

  • Implementation logic:

  • Create an empty object userConfig to store user animation configurations.

  • Assign the corresponding configuration to userConfig based on different animation configurations in spec (such as animationAppear, animationDisappear, etc.).

  • Call the uniformAnimationConfig function to unify animation configurations.

  • Return the generated user animation configuration.

  • Execute animation configurations one by one

utils.ts

function produceOneByOne(
  stateConfig: IAnimationTypeConfig,
  dataIndex: (datum: any, params: any) => number,
  dataCount?: () => number
) {
  // ... 函数实现 ...
}    

  • Parameters:

  • stateConfig: Configuration object for the animation type.

  • dataIndex: Function that returns the index of the data item in the animation sequence.

  • dataCount: Optional function that returns the total number of data items.

  • Implementation logic:

  • Destructure oneByOne, duration, delay, and delayAfter configurations from stateConfig.

  • Configure the delay time delay before the element appears, calculate the delay time based on the data item index and animation parameters.

  • Configure the delay time delayAfter after the element appears, also calculate the delay time based on the data item index and animation parameters.

  • Remove the no longer needed oneByOne parameter.

  • Return the updated animation configuration object.

  • Other auxiliary functions

  • defaultDataIndex: Get the default data index based on the data item or animation parameters.

  • shouldMarkDoMorph: Determine whether the specified mark should perform morphing animation.

  • isTimeLineAnimation and isChannelAnimation: Determine whether the animation configuration is a timeline animation or a channel animation.

  • uniformAnimationConfig: Unify the animation configuration, process and convert functions in the configuration.

  • traverseSpec: Traverse and transform the given object or array, applying the provided transformation function.

  • isAnimationEnabledForSeries: Determine whether the series has animation enabled, check based on series specifications, area animation properties, and data volume.

Summary

The animation orchestration implementation of VChart mainly merges and processes default configurations and user configurations through a series of functions and type definitions to generate the final animation configuration. At the same time, it provides functions such as executing animations one by one, morphing animations, and logic to determine whether animations are enabled, ensuring flexibility and configurability of animations in different scenarios.

Interpretation of Animation Orchestration Implementation

Animation orchestration refers to combining multiple animation tasks in a certain order or condition to form a coherent and complex animation sequence. In VChart, the design of animation orchestration allows developers to create multi-stage, multi-element collaborative animation effects, thereby enhancing the visual expressiveness and user experience of the chart. The following is a detailed interpretation of the implementation.

1. Concept of Animation Orchestration

Animation Orchestration is to enhance the effect of data visualization through carefully designed animation sequences. It is not just simple animation stacking, but also considers the coordination between animations, timeline management, and state transitions. VChart provides flexible tools to achieve animation orchestration, including but not limited to:

  • Chained Animation: Multiple animations are executed sequentially.

  • Parallel Animation: Multiple animations are executed simultaneously.

  • Conditional Trigger: Certain animations are triggered based on specific conditions.

  • Event Driven: Animations are triggered based on user interactions or other events.

2. Animation Configuration Structure

IAnimationSpec Interface

The IAnimationSpec interface defines the basic structure of animation configuration, which includes animation settings for different states. For animation orchestration, it mainly involves the following properties:

  • animationState: Used to describe state transition animations, can be used to build complex animation sequences.

  • animationNormal: Used to describe persistent loop animations, can be used as background animations in animation orchestration.

interface IAnimationSpec<MarkName extends string, Preset extends string> {
  animationState?: boolean | IStateAnimationConfig;
  animationNormal?: IMarkAnimateSpec<MarkName>;
}    

Each attribute can accept a boolean value (enable/disable), a preset configuration object, or a custom configuration object as a parameter, providing developers with a high degree of customization possibilities.

3. Animation Task Interface

IAnimationTask Interface

To support complex animation orchestration, VChart introduces the IAnimationTask interface to describe the data structure of animation tasks. Each task includes a time offset, an action queue, and a list of successor tasks, 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, achieving more complex and delicate animation effects.

4. Specific Implementation of Animation Orchestration

Taking the creation of a bar chart with animation orchestration as an example, suppose we want to achieve the following effects:

  • When the page loads, all bars grow from the bottom up;

  • After the bars finish growing, a pulse effect is added to the top to attract the user's attention;

  • If new data is added, new bars fade in, and existing bars slightly scale to indicate changes.

Step 1: Define Animation Configuration

First, specify animationAppear, animationEnter, animationUpdate, etc., in the chart configuration for the bar chart series. Here we can choose built-in animation types and adjust their duration and easing functions.

const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [/* 初始数据数组 */],
      animationAppear: {
        type: 'growCenterIn', // 柱子从中心向外生长
        duration: 1000,
        easing: 'easeInOutQuad'
      },
      animationNormal: {
        type: 'pulse', // 生长完成后顶部添加脉冲效果
        duration: 800,
        easing: 'easeInOutQuad'
      },
      animationEnter: {
        type: 'fadeIn', // 新数据点淡入
        duration: 800,
        easing: 'easeInOutQuad'
      },
      animationUpdate: {
        type: 'scaleIn', // 更新数据点缩放
        duration: 500,
        easing: 'easeInOutQuad'
      }
    }
  ]
};    

Step 2: Register Animation

Ensure that the required animations have 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_Grow, pulseAnimation, Appear_FadeIn, ScaleInOutAnimation } from './series/bar/animation';

// 注册柱子生长动画
Factory.registerAnimation('growCenterIn', Appear_Grow);

// 注册脉冲动画
Factory.registerAnimation('pulse', pulseAnimation);

// 注册淡入动画
Factory.registerAnimation('fadeIn', Appear_FadeIn);

// 注册缩放动画
Factory.registerAnimation('scaleIn', ScaleInOutAnimation);    

Step 3: 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'   // 使用浅色主题
  }
});    

Step 4: Build Animation Choreography

To achieve animation choreography, we need to construct a task chain that includes multiple animation tasks. Each task can be a single animation or a composite animation (i.e., containing multiple subtasks). Here are the specific implementation steps:

  • Define Animation Tasks: First, define each individual animation task, including their time offsets, action queues, and successor task lists.
const appearTask: IAnimationTask = {
  timeOffset: 0,
  actionList: [{ type: 'growCenterIn', duration: 1000 }],
  nextTaskList: [normalTask]
};

const normalTask: IAnimationTask = {
  timeOffset: 0,
  actionList: [{ type: 'pulse', duration: 800, loop: true }],
  nextTaskList: []
};

const enterTask: IAnimationTask = {
  timeOffset: 0,
  actionList: [{ type: 'fadeIn', duration: 800 }],
  nextTaskList: []
};

const updateTask: IAnimationTask = {
  timeOffset: 0,
  actionList: [{ type: 'scaleIn', duration: 500 }],
  nextTaskList: []
};    

  • Composite Animation Tasks: Next, combine these tasks into a complete animation choreography. For example, we can create a task chain that includes entrance animations and animations in the normal state.
const animationArrangement: IAnimationTask = {
  timeOffset: 0,
  actionList: [],
  nextTaskList: [appearTask, normalTask]
};    

Step 5: Trigger Data Update Animation

Once the chart is rendered, any changes in the data will automatically trigger animations. For example, when new data is added, the enter task will be triggered; when data is updated, the update task takes effect; and when data is removed, the exit task comes into play.

// 模拟数据更新
setTimeout(() => {
  const updatedData = [
    { value: 15 }, // 更新第一个数据点
    { value: 25 }, // 更新第二个数据点
    { value: 35 }, // 更新第三个数据点
    { value: 45 }  // 添加一个新的数据点
  ];

  // 更新图表数据并触发动画
  chart.updateSeriesData(updatedData);
}, 5000);    

In this example, the updateSeriesData method triggers a series of animations:

  • For newly added data points (the fourth data point), the enter task will make it gradually appear with a fade-in effect.

  • For existing data points (the first three data points), the update task will adjust their size based on the new data values and transition with a scaling effect.

Step 6: Dynamically Control Animation Orchestration

In some cases, you may want to dynamically control the behavior of animation orchestration, such as changing the speed or style of the animation. VChart provides flexible methods to achieve this.

// 更新某个系列的动画编排配置
chart.updateSeriesOptions(0, {
  animationAppear: {
    type: 'growCenterIn',
    duration: 1200, // 更改持续时间
    easing: 'linear' // 更改缓动函数
  },
  animationNormal: {
    type: 'pulse',
    duration: 1000, // 更改持续时间
    easing: 'easeInOutCubic' // 更改缓动函数
  }
});

// 重新应用新的动画配置
chart.render();    

5. Internal Implementation of Animation Orchestration

AnimateManager Class

The AnimateManager class is responsible for managing and coordinating the state of all animations. It implements the IAnimate interface and provides methods to update and retrieve animation states. For animation orchestration, AnimateManager ensures that these animation tasks are executed in a predetermined order or condition.

class AnimateManager extends StateManager implements IAnimate {
  updateAnimateState(state: AnimationStateEnum, noRender?: boolean) {
    if (state === AnimationStateEnum.appear) {
      this.updateState(
        {
          animationState: {
            callback: (datum: any, element: IElement) => state
          }
        },
        noRender
      );
    } else if (state === AnimationStateEnum.normal) {
      this.updateState(
        {
          animationState: {
            callback: (datum: any, element: IElement) => state
          }
        },
        noRender
      );
    }
  }

  // 动画编排逻辑
  arrangeAnimations(tasks: IAnimationTask[]) {
    tasks.forEach(task => {
      // 执行当前任务的动作队列
      task.actionList.forEach(action => {
        this.executeAction(action);
      });

      // 如果存在后继任务,则递归执行
      if (task.nextTaskList && task.nextTaskList.length > 0) {
        setTimeout(() => {
          this.arrangeAnimations(task.nextTaskList);
        }, task.timeOffset);
      }
    });
  }

  private executeAction(action: Action) {
    // 根据action.type获取对应的动画配置
    const animationConfig = Factory.getAnimationInKey(action.type);

    // 应用动画配置到目标元素
    this.applyAnimation(animationConfig, action.duration, action.easing);
  }

  private applyAnimation(config: MarkAnimationSpec, duration: number, easing: string) {
    // 实际应用动画的逻辑
  }
}    

This code demonstrates how to execute a set of animation tasks using the arrangeAnimations method. The action queue in each task will be executed one by one, and then subsequent tasks will be recursively processed according to the timeOffset attribute. This allows for the construction of an ordered animation sequence, achieving complex animation orchestration effects.

6. Advanced Features of Animation Orchestration

Conditional Triggering and Event Listening

To increase the flexibility of animation orchestration, VChart also provides conditional triggering and event listening features. For example, animations can be triggered by listening to user interaction events (such as clicks, hovers), or dynamically adjust animation behavior based on specific conditions (such as data thresholds).

// 监听用户交互事件
chart.on('element:click', (event) => {
  const element = event.detail.element;
  if (element) {
    // 根据点击事件触发动画
    this.triggerCustomAnimation(element);
  }
});

// 条件触发动画
if (someCondition) {
  // 触发特定条件下的动画
  this.triggerConditionalAnimation();
}    

Parallel Animation

Sometimes, we want multiple animations to occur simultaneously rather than waiting for each to finish in sequence. VChart supports parallel animations, allowing developers to define multiple animation tasks to start executing at the same time.

const parallelTasks: IAnimationTask[] = [
  {
    timeOffset: 0,
    actionList: [{ type: 'growCenterIn', duration: 1000 }],
    nextTaskList: []
  },
  {
    timeOffset: 0,
    actionList: [{ type: 'pulse', duration: 800, loop: true }],
    nextTaskList: []
  }
];

this.arrangeAnimations(parallelTasks);    

Delay and Interval

By setting the timeOffset property, you can control the delay time between animation tasks. Additionally, you can use setInterval or setTimeout to achieve more complex timing logic.

// 设置延时
const delayedTask: IAnimationTask = {
  timeOffset: 500, // 延迟500毫秒后执行
  actionList: [{ type: 'pulse', duration: 800, loop: true }],
  nextTaskList: []
};

this.arrangeAnimations([delayedTask]);

// 使用 setInterval 实现周期性动画
setInterval(() => {
  this.triggerPeriodicAnimation();
}, 2000); // 每2秒触发一次    

7. Example: Creating a Bar Chart with Animation Orchestration

Below is an example of creating a bar chart with animation orchestration, illustrating how to use VChart's animation orchestration system to achieve the basic process.

Example: Creating a Bar Chart with Animation Orchestration

In VChart, animation orchestration refers to the combination and sequencing of multiple animation effects to achieve complex and coordinated visual effects. Through proper animation orchestration, the interactivity and user experience of the chart can be significantly enhanced. Below we will demonstrate in detail how to create a bar chart with animation orchestration, including the entrance animation for new data points, the update animation for existing data points, and the exit animation for old data points.

1. Define Animation Configuration

First, we need to define the basic configuration of the bar chart and specify specific animation effects for each animation state (enter, update, exit). To achieve complex animation orchestration, we can use chained animation tasks to define the specific animation sequence for each state.

const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [
        { category: 'A', value: 10 },
        { category: 'B', value: 20 },
        { category: 'C', value: 30 }
      ],
      animationEnter: {
        type: 'fadeIn',
        duration: 800,
        easing: 'easeInOutQuad',
        nextTaskList: [
          {
            timeOffset: 800,
            actionList: [
              { type: 'growCenterIn', duration: 500, easing: 'easeInOutQuad' }
            ],
            nextTaskList: [
              {
                timeOffset: 500,
                actionList: [
                  { type: 'pulse', duration: 300, easing: 'easeInOutQuad' }
                ]
              }
            ]
          }
        ]
      },
      animationUpdate: {
        type: 'scaleIn',
        duration: 500,
        easing: 'easeInOutQuad'
      },
      animationExit: {
        type: 'fadeOut',
        duration: 600,
        easing: 'easeInOutQuad'
      }
    }
  ]
};    

In this configuration:

  • **animationEnter**: New data points first fade in (fadeIn), then grow outward from the center (growCenterIn), and finally pulse slightly (pulse).

  • **animationUpdate**: Existing data points transition with scaling when updated.

  • **animationExit**: Old data points disappear by fading out.

2. Register Animation

Next, we need to ensure that the required animations have 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, ScaleInOutAnimation, Appear_FadeOut, growCenterIn, pulseAnimation } from './series/bar/animation';

// 注册淡入动画
Factory.registerAnimation('fadeIn', Appear_FadeIn);

// 注册缩放动画
Factory.registerAnimation('scaleIn', ScaleInOutAnimation);

// 注册淡出动画
Factory.registerAnimation('fadeOut', Appear_FadeOut);

// 注册中心生长动画
Factory.registerAnimation('growCenterIn', growCenterIn);

// 注册脉冲动画
Factory.registerAnimation('pulse', pulseAnimation);    

These animation functions define the specific logic for fade-in, zoom, fade-out, center growth, and pulse animations respectively. For example, the Appear_FadeIn function might look like this:

export const Appear_FadeIn: IAnimationTypeConfig = {
  type: 'fadeIn',
  duration: 800,
  easing: 'easeInOutQuad',
  channel: {
    opacity: { from: 0, to: 1 }
  }
};

export const growCenterIn: IAnimationTypeConfig = {
  type: 'growCenterIn',
  duration: 500,
  easing: 'easeInOutQuad',
  channel: {
    width: { from: 0, to: '100%' },
    height: { from: 0, to: '100%' }
  }
};

export const pulseAnimation: IAnimationTypeConfig = {
  type: 'pulse',
  duration: 300,
  easing: 'easeInOutQuad',
  channel: {
    scale: { from: 1, to: 1.1, toBack: 1 }
  }
};    

3. Initialize 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'   // 使用浅色主题
  }
});    

4. Trigger Animation

Once the chart is rendered, any changes in the data will automatically trigger animations. For example, when new data is added, the animationEnter configuration will take effect; when data is updated, the animationUpdate configuration is effective; and when data is removed, the animationExit configuration comes into play.

// 模拟数据更新
setTimeout(() => {
  const updatedData = [
    { category: 'A', value: 15 }, // 更新第一个数据点
    { category: 'B', value: 25 }, // 更新第二个数据点
    { category: 'C', value: 35 }, // 更新第三个数据点
    { category: 'D', value: 45 }  // 添加一个新的数据点
  ];

  // 更新图表数据并触发动画
  chart.updateSeriesData(updatedData);
}, 5000);    

In this example, the updateSeriesData method will trigger a series of animations:

  • New Data Points (D):
  • Fade In (fadeIn): Gradually change from opacity 0 to 1.

  • Grow Center In (growCenterIn): Grow outward from the center, with width and height changing from 0 to the final value.

  • Pulse (pulse): Slightly enlarge and then return to the original state to attract the user's attention.

  • Existing Data Points (A, B, C):

  • Scale In (scaleIn): Adjust the height of the columns according to the new data values for a smooth transition.

5. Detailed Implementation of Animation Orchestration

Chained Animation Tasks

To achieve complex animation orchestration, we can use the IAnimationTask interface to define a sequence of animation tasks for each state. Each task includes time offset, action queue, and a list of successor tasks, forming a chained animation execution mechanism.

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

Example: Defining a Chained Animation Task

Suppose we want to define a complex chained animation task for new data points in a bar chart, starting with a fade-in, followed by a center growth, and finally a slight pulse effect.

const enterAnimationTasks: IAnimationTask[] = [
  {
    timeOffset: 0,
    actionList: [
      { type: 'fadeIn', duration: 800, easing: 'easeInOutQuad' }
    ],
    nextTaskList: [
      {
        timeOffset: 800,
        actionList: [
          { type: 'growCenterIn', duration: 500, easing: 'easeInOutQuad' }
        ],
        nextTaskList: [
          {
            timeOffset: 500,
            actionList: [
              { type: 'pulse', duration: 300, easing: 'easeInOutQuad' }
            ]
          }
        ]
      }
    ]
  }
];    

Using Chained Animation Tasks in Chart Configuration

Integrate the defined chained animation tasks into the chart configuration to ensure that new data points execute animations in the expected order and effect.

const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [
        { category: 'A', value: 10 },
        { category: 'B', value: 20 },
        { category: 'C', value: 30 }
      ],
      animationEnter: enterAnimationTasks,
      animationUpdate: {
        type: 'scaleIn',
        duration: 500,
        easing: 'easeInOutQuad'
      },
      animationExit: {
        type: 'fadeOut',
        duration: 600,
        easing: 'easeInOutQuad'
      }
    }
  ]
};    

6. Execution of Animation Tasks

Parsing and Execution of Animation Tasks

VChart internally parses the animation tasks in animationEnter, animationUpdate, and animationExit, and executes the corresponding animations according to the defined order and time offset. Below is a simplified example showing how to parse and execute chained animation tasks.

class AnimateManager extends StateManager implements IAnimate {
  updateAnimateState(state: AnimationStateEnum, noRender?: boolean) {
    if (state === AnimationStateEnum.update) {
      this.updateState(
        {
          animationState: {
            callback: (datum: any, element: IElement) => element.diffState
          }
        },
        noRender
      );
    } else if (state === AnimationStateEnum.appear) {
      // 处理新数据点的入场动画
      this.handleAnimationTasks(element, element.animationConfig.enter);
    } else if (state === AnimationStateEnum.exit) {
      // 处理旧数据点的退场动画
      this.handleAnimationTasks(element, element.animationConfig.exit);
    }
  }

  private handleAnimationTasks(element: IElement, tasks: IAnimationTask[]) {
    tasks.forEach(task => {
      setTimeout(() => {
        task.actionList.forEach(action => {
          element.startAnimation(action.type, action.duration, action.easing);
        });
        if (task.nextTaskList) {
          this.handleAnimationTasks(element, task.nextTaskList);
        }
      }, task.timeOffset);
    });
  }
}    

In this example, the handleAnimationTasks method recursively parses and executes each animation task, ensuring that the corresponding animations are triggered in the defined order and time offset.

7. Specific Implementation of Animations

Definition of Animation Functions

Each specific animation function (such as Appear_FadeIn, ScaleInOutAnimation, Appear_FadeOut, growCenterIn, and pulseAnimation) defines the specific behavior of the animation. Here are some examples of specific animation functions:

// 淡入动画
export const Appear_FadeIn: IAnimationTypeConfig = {
  type: 'fadeIn',
  duration: 800,
  easing: 'easeInOutQuad',
  channel: {
    opacity: { from: 0, to: 1 }
  }
};

// 缩放动画
export const ScaleInOutAnimation: IAnimationTypeConfig = {
  type: 'scaleIn',
  duration: 500,
  easing: 'easeInOutQuad',
  channel: {
    scale: { from: 0.8, to: 1 }
  }
};

// 淡出动画
export const Appear_FadeOut: IAnimationTypeConfig = {
  type: 'fadeOut',
  duration: 600,
  easing: 'easeInOutQuad',
  channel: {
    opacity: { from: 1, to: 0 }
  }
};

// 中心生长动画
export const growCenterIn: IAnimationTypeConfig = {
  type: 'growCenterIn',
  duration: 500,
  easing: 'easeInOutQuad',
  channel: {
    width: { from: 0, to: '100%' },
    height: { from: 0, to: '100%' }
  }
};

// 脉冲动画
export const pulseAnimation: IAnimationTypeConfig = {
  type: 'pulse',
  duration: 300,
  easing: 'easeInOutQuad',
  channel: {
    scale: { from: 1, to: 1.1, toBack: 1 }
  }
};    

Registration of Animation Functions

Ensure that these animation functions have been correctly registered in the system so that they can be called when needed.

import { Factory } from '@visactor/vchart';
import { Appear_FadeIn, ScaleInOutAnimation, Appear_FadeOut, growCenterIn, pulseAnimation } from './series/bar/animation';

Factory.registerAnimation('fadeIn', Appear_FadeIn);
Factory.registerAnimation('scaleIn', ScaleInOutAnimation);
Factory.registerAnimation('fadeOut', Appear_FadeOut);
Factory.registerAnimation('growCenterIn', growCenterIn);
Factory.registerAnimation('pulse', pulseAnimation);    

8. Complete Example Code

Below is a complete example code demonstrating how to create a bar chart with complex animation choreography.

// 导入必要的模块
import { VChart } from '@visactor/vchart';
import { Factory } from '@visactor/vchart';
import { IElement, IAnimationTypeConfig } from '@visactor/vgrammar-core';

// 定义动画函数
export const Appear_FadeIn: IAnimationTypeConfig = {
  type: 'fadeIn',
  duration: 800,
  easing: 'easeInOutQuad',
  channel: {
    opacity: { from: 0, to: 1 }
  }
};

export const ScaleInOutAnimation: IAnimationTypeConfig = {
  type: 'scaleIn',
  duration: 500,
  easing: 'easeInOutQuad',
  channel: {
    scale: { from: 0.8, to: 1 }
  }
};

export const Appear_FadeOut: IAnimationTypeConfig = {
  type: 'fadeOut',
  duration: 600,
  easing: 'easeInOutQuad',
  channel: {
    opacity: { from: 1, to: 0 }
  }
};

export const growCenterIn: IAnimationTypeConfig = {
  type: 'growCenterIn',
  duration: 500,
  easing: 'easeInOutQuad',
  channel: {
    width: { from: 0, to: '100%' },
    height: { from: 0, to: '100%' }
  }
};

export const pulseAnimation: IAnimationTypeConfig = {
  type: 'pulse',
  duration: 300,
  easing: 'easeInOutQuad',
  channel: {
    scale: { from: 1, to: 1.1, toBack: 1 }
  }
};

// 注册动画
Factory.registerAnimation('fadeIn', Appear_FadeIn);
Factory.registerAnimation('scaleIn', ScaleInOutAnimation);
Factory.registerAnimation('fadeOut', Appear_FadeOut);
Factory.registerAnimation('growCenterIn', growCenterIn);
Factory.registerAnimation('pulse', pulseAnimation);

// 定义链式动画任务
const enterAnimationTasks: IAnimationTask[] = [
  {
    timeOffset: 0,
    actionList: [
      { type: 'fadeIn', duration: 800, easing: 'easeInOutQuad' }
    ],
    nextTaskList: [
      {
        timeOffset: 800,
        actionList: [
          { type: 'growCenterIn', duration: 500, easing: 'easeInOutQuad' }
        ],
        nextTaskList: [
          {
            timeOffset: 500,
            actionList: [
              { type: 'pulse', duration: 300, easing: 'easeInOutQuad' }
            ]
          }
        ]
      }
    ]
  }
];

// 定义图表配置
const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [
        { category: 'A', value: 10 },
        { category: 'B', value: 20 },
        { category: 'C', value: 30 }
      ],
      animationEnter: enterAnimationTasks,
      animationUpdate: {
        type: 'scaleIn',
        duration: 500,
        easing: 'easeInOutQuad'
      },
      animationExit: {
        type: 'fadeOut',
        duration: 600,
        easing: 'easeInOutQuad'
      }
    }
  ]
};

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

// 模拟数据更新
setTimeout(() => {
  const updatedData = [
    { category: 'A', value: 15 }, // 更新第一个数据点
    { category: 'B', value: 25 }, // 更新第二个数据点
    { category: 'C', value: 35 }, // 更新第三个数据点
    { category: 'D', value: 45 }  // 添加一个新的数据点
  ];

  // 更新图表数据并触发动画
  chart.updateSeriesData(updatedData);
}, 5000);    

9. Advanced Usage of Animation Orchestration

Conditional Animation Configuration

Conditional Animation Configuration allows dynamically selecting different animation effects based on specific attributes of data points. For example, when a data value exceeds a certain threshold, a special animation is used; otherwise, the default animation is used. VChart allows you to embed logical judgments in the configuration to achieve such requirements.

const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [
        { category: 'A', value: 10 },
        { category: 'B', value: 60 },
        { category: 'C', value: 30 }
      ],
      animationEnter: (datum: any) => {
        if (datum.value > 50) {
          return {
            type: 'specialGrowth', // 特殊的生长动画
            duration: 1000,
            easing: 'easeInOutQuad'
          };
        } else {
          return {
            type: 'fadeIn', // 默认的淡入动画
            duration: 800,
            easing: 'easeInOutQuad'
          };
        }
      },
      animationUpdate: {
        type: 'scaleIn',
        duration: 500,
        easing: 'easeInOutQuad'
      },
      animationExit: {
        type: 'fadeOut',
        duration: 600,
        easing: 'easeInOutQuad'
      }
    }
  ]
};    

In this example, the animationEnter configuration accepts a function as a parameter, which can return different animation configuration objects based on the specific attributes of the data points. Specifically:

  • Data point B has a value of 60, which is greater than the threshold of 50, so the specialGrowth animation is used.

  • Data points A and C have values of 10 and 30 respectively, which are less than the threshold of 50, so the fadeIn animation is used.

Custom Animation Types

In addition to using built-in animation types, VChart also supports developers in customizing animation logic. You can create new animation effects by inheriting or extending existing animation classes and register them into the system.

// 定义一个新的动画类型
function specialGrowthAnimation(params: any): IAnimationTypeConfig {
  return {
    type: 'specialGrowth',
    duration: 1000,
    easing: 'easeInOutQuad',
    channel: {
      width: { from: 0, to: params.width },
      height: { from: 0, to: params.height },
      opacity: { from: 0, to: 1 }
    }
  };
}

// 注册自定义动画
Factory.registerAnimation('specialGrowth', specialGrowthAnimation);

// 在图表配置中使用自定义动画
const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [
        { category: 'A', value: 10 },
        { category: 'B', value: 60 },
        { category: 'C', value: 30 }
      ],
      animationEnter: (datum: any) => {
        if (datum.value > 50) {
          return {
            type: 'specialGrowth',
            duration: 1000,
            easing: 'easeInOutQuad'
          };
        } else {
          return {
            type: 'fadeIn',
            duration: 800,
            easing: 'easeInOutQuad'
          };
        }
      },
      animationUpdate: {
        type: 'scaleIn',
        duration: 500,
        easing: 'easeInOutQuad'
      },
      animationExit: {
        type: 'fadeOut',
        duration: 600,
        easing: 'easeInOutQuad'
      }
    }
  ]
};    

In this example, we define a custom animation named specialGrowth and register it into the system. Then, in the animationEnter configuration, we dynamically choose to use either the specialGrowth or fadeIn animation based on the value of the data point.

10. Advanced Usage of Animation Tasks

Nested Animation Tasks

In addition to simple chained animation tasks, VChart also supports nested animation tasks, making animation orchestration more flexible and complex. Through nested tasks, more precise animation control can be achieved.

Example: Nested Animation Tasks

Suppose we want to create a more complex animation sequence for newly added data points, starting with a fade-in, followed by a center growth, then a slight pulse effect, and finally a highlight.

const enterAnimationTasks: IAnimationTask[] = [
  {
    timeOffset: 0,
    actionList: [
      { type: 'fadeIn', duration: 800, easing: 'easeInOutQuad' }
    ],
    nextTaskList: [
      {
        timeOffset: 800,
        actionList: [
          { type: 'growCenterIn', duration: 500, easing: 'easeInOutQuad' }
        ],
        nextTaskList: [
          {
            timeOffset: 500,
            actionList: [
              { type: 'pulse', duration: 300, easing: 'easeInOutQuad' }
            ],
            nextTaskList: [
              {
                timeOffset: 300,
                actionList: [
                  { type: 'highlight', duration: 500, easing: 'easeInOutQuad' }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
];    

In this nested animation task:

  • Fade In (**fadeIn**): Gradually changes from opacity 0 to 1.

  • Grow Center In (**growCenterIn**): Grows outward from the center, with width and height changing from 0 to the final value.

  • Pulse (**pulse**): Slightly enlarges and then returns to its original state to attract the user's attention.

  • Highlight (**highlight**): Adds a highlight effect to the data point after the animation ends.

Define Highlight Animation

First, define and register the highlight animation.

export const highlightAnimation: IAnimationTypeConfig = {
  type: 'highlight',
  duration: 500,
  easing: 'easeInOutQuad',
  channel: {
    fill: { from: 'blue', to: 'red', toBack: 'blue' }
  }
};

// 注册高亮显示动画
Factory.registerAnimation('highlight', highlightAnimation);    

Use Nested Animation Tasks in Chart Configuration

Integrate the defined nested animation tasks into the chart configuration.

const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [
        { category: 'A', value: 10 },
        { category: 'B', value: 20 },
        { category: 'C', value: 30 }
      ],
      animationEnter: enterAnimationTasks,
      animationUpdate: {
        type: 'scaleIn',
        duration: 500,
        easing: 'easeInOutQuad'
      },
      animationExit: {
        type: 'fadeOut',
        duration: 600,
        easing: 'easeInOutQuad'
      }
    }
  ]
};    

11. Execution Mechanism of Animation Tasks

Parsing and Execution of Animation Tasks

VChart internally parses the animation tasks in animationEnter, animationUpdate, and animationExit, and executes the corresponding animations according to the defined order and time offset. Below is a simplified example showing how to parse and execute chained animation tasks.

class AnimateManager extends StateManager implements IAnimate {
  updateAnimateState(state: AnimationStateEnum, noRender?: boolean) {
    if (state === AnimationStateEnum.update) {
      this.updateState(
        {
          animationState: {
            callback: (datum: any, element: IElement) => element.diffState
          }
        },
        noRender
      );
    } else if (state === AnimationStateEnum.appear) {
      // 处理新数据点的入场动画
      this.handleAnimationTasks(element, element.animationConfig.enter);
    } else if (state === AnimationStateEnum.exit) {
      // 处理旧数据点的退场动画
      this.handleAnimationTasks(element, element.animationConfig.exit);
    }
  }

  private handleAnimationTasks(element: IElement, tasks: IAnimationTask[]) {
    tasks.forEach(task => {
      setTimeout(() => {
        task.actionList.forEach(action => {
          element.startAnimation(action.type, action.duration, action.easing);
        });
        if (task.nextTaskList) {
          this.handleAnimationTasks(element, task.nextTaskList);
        }
      }, task.timeOffset);
    });
  }
}    

In this example, the handleAnimationTasks method recursively parses and executes each animation task, ensuring that the corresponding animations are triggered in the defined order and time offset.

Timing of Animation Task Triggering

To ensure animations are triggered at the appropriate time, VChart provides a series of hook functions, such as VGRAMMAR_HOOK_EVENT.AFTER_DO_RENDER and VGRAMMAR_HOOK_EVENT.ANIMATION_END. These hooks can help us execute specific logic when the chart is first rendered or when the animation ends.

this._event.on(VGRAMMAR_HOOK_EVENT.AFTER_DO_RENDER, () => {
  // 图表首次渲染完成后的逻辑
  console.log('图表首次渲染完成');
});

this._event.on(VGRAMMAR_HOOK_EVENT.ANIMATION_END, ({ event }) => {
  if (event.animationState === AnimationStateEnum.enter) {
    // enter 动画结束后的逻辑
    console.log('新数据点入场动画结束');
  } else if (event.animationState === AnimationStateEnum.update) {
    // update 动画结束后的逻辑
    console.log('现有数据点更新动画结束');
  } else if (event.animationState === AnimationStateEnum.exit) {
    // exit 动画结束后的逻辑
    console.log('旧数据点退场动画结束');
  }
});    

12. Best Practices for Animation Coordination

Batch Update Data

To improve performance, it is recommended to minimize frequent data update operations. If you need to update a large amount of data, consider merging these updates into a single batch operation to reduce unnecessary rendering times.

// 不推荐的做法:逐个更新数据点
data.forEach((item, index) => {
  setTimeout(() => {
    chart.updateSeriesData([/* 更新后的数据 */]);
  }, index * 100); // 每隔100毫秒更新一个数据点
});

// 推荐的做法:一次性批量更新所有数据
setTimeout(() => {
  const updatedData = data.map(item => /* 更新后的数据 */);
  chart.updateSeriesData(updatedData);
}, 1000); // 1秒后一次性更新所有数据    

Lazy Load Animation

For scenarios with large charts or a large number of data points, lazy loading can be used to delay loading animations until user interaction or specific conditions are met. This helps improve initial loading speed and overall performance.

// 懒加载动画配置
const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [/* 大量数据数组 */],
      animationEnter: {
        type: 'lazyFadeIn',
        duration: 800,
        easing: 'easeInOutQuad',
        lazyLoad: true // 启用懒加载
      }
    }
  ]
};

// 当用户滚动到视口内时触发懒加载动画
window.addEventListener('scroll', () => {
  if (isInViewPort(chartContainer)) {
    chart.startLazyAnimations();
  }
});    

Cache Animation Results

For those animation effects with high computational cost, consider caching their results to avoid repeated calculations. For example, for complex path animations, you can pre-calculate the keyframes of the path and reuse these keyframes in subsequent rendering.

class PathAnimator {
  private cachedFrames: KeyFrame[];

  constructor(private pathData: PathData) {
    this.cachedFrames = this.computeKeyFrames(pathData);
  }

  private computeKeyFrames(data: PathData): KeyFrame[] {
    // 计算路径的关键帧并返回
  }

  public animate(element: IElement): void {
    // 使用缓存的关键帧进行动画
    this.applyCachedFrames(element);
  }
}    

Event Throttling and Debouncing

To avoid performance issues caused by frequent event triggering, you can apply throttling or debouncing techniques to event listeners. For example, when handling mouse hover events, you can limit the frequency of animation triggers.

import throttle from 'lodash/throttle';

// 对鼠标悬停事件应用节流
chart.on('element:hover', throttle((event) => {
  // 触发悬停动画
}, 200)); // 每200毫秒最多触发一次    

Dynamic Control of Animation

In some cases, you may want to dynamically control the behavior of animations, such as changing the speed or style of the animation. VChart provides flexible methods to achieve this.

// 更新某个系列的动画配置
chart.updateSeriesOptions(0, {
  animationEnter: {
    duration: 1000, // 更改淡入动画的持续时间
    easing: 'linear' // 更改缓动函数
  },
  animationUpdate: {
    duration: 700, // 更改缩放动画的持续时间
    easing: 'easeInOutCubic' // 更改缓动函数
  },
  animationExit: {
    duration: 900, // 更改淡出动画的持续时间
    easing: 'easeInOutCubic' // 更改缓动函数
  }
});

// 重新应用新的动画配置
chart.render();    

13. Complete Example Code

Below is a complete example code demonstrating how to create a bar chart with complex animation choreography, implementing conditional animation configuration and custom animation types.

// 导入必要的模块
import { VChart } from '@visactor/vchart';
import { Factory } from '@visactor/vchart';
import { IElement, IAnimationTypeConfig } from '@visactor/vgrammar-core';

// 定义动画函数
export const Appear_FadeIn: IAnimationTypeConfig = {
  type: 'fadeIn',
  duration: 800,
  easing: 'easeInOutQuad',
  channel: {
    opacity: { from: 0, to: 1 }
  }
};

export const ScaleInOutAnimation: IAnimationTypeConfig = {
  type: 'scaleIn',
  duration: 500,
  easing: 'easeInOutQuad',
  channel: {
    scale: { from: 0.8, to: 1 }
  }
};

export const Appear_FadeOut: IAnimationTypeConfig = {
  type: 'fadeOut',
  duration: 600,
  easing: 'easeInOutQuad',
  channel: {
    opacity: { from: 1, to: 0 }
  }
};

export const growCenterIn: IAnimationTypeConfig = {
  type: 'growCenterIn',
  duration: 500,
  easing: 'easeInOutQuad',
  channel: {
    width: { from: 0, to: '100%' },
    height: { from: 0, to: '100%' }
  }
};

export const pulseAnimation: IAnimationTypeConfig = {
  type: 'pulse',
  duration: 300,
  easing: 'easeInOutQuad',
  channel: {
    scale: { from: 1, to: 1.1, toBack: 1 }
  }
};

export const highlightAnimation: IAnimationTypeConfig = {
  type: 'highlight',
  duration: 500,
  easing: 'easeInOutQuad',
  channel: {
    fill: { from: 'blue', to: 'red', toBack: 'blue' }
  }
};

// 注册动画
Factory.registerAnimation('fadeIn', Appear_FadeIn);
Factory.registerAnimation('scaleIn', ScaleInOutAnimation);
Factory.registerAnimation('fadeOut', Appear_FadeOut);
Factory.registerAnimation('growCenterIn', growCenterIn);
Factory.registerAnimation('pulse', pulseAnimation);
Factory.registerAnimation('highlight', highlightAnimation);

// 定义链式动画任务
const enterAnimationTasks: IAnimationTask[] = [
  {
    timeOffset: 0,
    actionList: [
      { type: 'fadeIn', duration: 800, easing: 'easeInOutQuad' }
    ],
    nextTaskList: [
      {
        timeOffset: 800,
        actionList: [
          { type: 'growCenterIn', duration: 500, easing: 'easeInOutQuad' }
        ],
        nextTaskList: [
          {
            timeOffset: 500,
            actionList: [
              { type: 'pulse', duration: 300, easing: 'easeInOutQuad' }
            ],
            nextTaskList: [
              {
                timeOffset: 300,
                actionList: [
                  { type: 'highlight', duration: 500, easing: 'easeInOutQuad' }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
];

// 定义图表配置
const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [
        { category: 'A', value: 10 },
        { category: 'B', value: 20 },
        { category: 'C', value: 30 }
      ],
      animationEnter: enterAnimationTasks,
      animationUpdate: {
        type: 'scaleIn',
        duration: 500,
        easing: 'easeInOutQuad'
      },
      animationExit: {
        type: 'fadeOut',
        duration: 600,
        easing: 'easeInOutQuad'
      }
    }
  ]
};

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

// 模拟数据更新
setTimeout(() => {
  const updatedData = [
    { category: 'A', value: 15 }, // 更新第一个数据点
    { category: 'B', value: 25 }, // 更新第二个数据点
    { category: 'C', value: 35 }, // 更新第三个数据点
    { category: 'D', value: 65    

In this example, the animationEnter configuration accepts a function as a parameter, which can return different values based on the specific attributes of the data points.

Continuing to Interpret the Implementation of Data Update Animation

In the previous section, we have detailed the basic concepts and implementation methods of data update animation in VChart. Next, we will delve into some more specific details, including how to handle complex animation sequences, advanced usage of animation configuration, and best practices for optimizing performance.

1. Handling Complex Animation Sequences

Chained Animation Tasks

For complex animation sequences, VChart introduces the IAnimationTask interface to describe the data structure of animation tasks. Each task includes a time offset, an action queue, and a list of successor tasks, forming a chained animation execution mechanism.

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

This design allows multiple animation tasks to be executed sequentially or concurrently, achieving more complex and subtle animation effects. For example, in a bar chart, we can define a series of consecutive animation tasks, first letting the newly added data points fade in, then gradually grow to the final height, and finally add some decorative animations (such as highlighting).

Example: Creating Chained Animations

Suppose we want to create a chained entry animation for new data points in a bar chart, starting with a fade-in, followed by growth, and finally a slight pulse effect to attract the user's attention.

const enterAnimationTasks: IAnimationTask[] = [
  {
    timeOffset: 0,
    actionList: [
      { type: 'fadeIn', duration: 800, easing: 'easeInOutQuad' }
    ],
    nextTaskList: [
      {
        timeOffset: 800,
        actionList: [
          { type: 'growCenterIn', duration: 500, easing: 'easeInOutQuad' }
        ],
        nextTaskList: [
          {
            timeOffset: 500,
            actionList: [
              { type: 'pulse', duration: 300, easing: 'easeInOutQuad' }
            ]
          }
        ]
      }
    ]
  }
];    

In this example, we use the enterAnimationTasks array to define a series of animation tasks, each with its own time offset, action queue, and list of successor tasks. In this way, very rich visual effects can be achieved.

2. Advanced Usage of Animation Configuration

Conditional Animation Configuration

Sometimes, you may want to dynamically choose different animation effects based on certain conditions. For example, when a data value exceeds a certain threshold, use a special animation; otherwise, use the default animation. VChart allows you to embed logical judgments in the configuration to achieve such requirements.

const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [/* 数据数组 */],
      animationEnter: (datum: any) => {
        if (datum.value > 50) {
          return {
            type: 'specialGrowth', // 特殊的生长动画
            duration: 1000,
            easing: 'easeInOutQuad'
          };
        } else {
          return {
            type: 'fadeIn', // 默认的淡入动画
            duration: 800,
            easing: 'easeInOutQuad'
          };
        }
      },
      animationUpdate: {
        type: 'scaleIn',
        duration: 500,
        easing: 'easeInOutQuad'
      },
      animationExit: {
        type: 'fadeOut',
        duration: 600,
        easing: 'easeInOutQuad'
      }
    }
  ]
};    

In this example, the animationEnter configuration accepts a function as a parameter, which can return different animation configuration objects based on the specific attributes of the data points. This allows the animation behavior to be dynamically adjusted according to the actual data, enhancing the expressiveness of the chart.

Custom Animation Types

In addition to using built-in animation types, VChart also supports developers in customizing animation logic. You can create new animation effects by inheriting or extending existing animation classes and registering them into the system.

import { Factory } from '@visactor/vchart';
import { IElement, IAnimationTypeConfig } from '@visactor/vgrammar-core';

// 定义一个新的动画类型
function customGrowAnimation(params: any): IAnimationTypeConfig {
  return {
    type: 'customGrow',
    duration: 1000,
    easing: 'easeInOutQuad',
    channel: {
      width: { from: 0, to: params.width },
      height: { from: 0, to: params.height }
    }
  };
}

// 注册自定义动画
Factory.registerAnimation('customGrow', customGrowAnimation);

// 在图表配置中使用自定义动画
const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [/* 数据数组 */],
      animationEnter: {
        type: 'customGrow',
        width: 50,
        height: 100
      }
    }
  ]
};    

This code demonstrates how to define and register a custom animation named customGrow, which adjusts the width and height of graphic elements based on the parameters passed. Then, this custom animation can be directly used in the chart configuration.

3. Performance Optimization and Best Practices

Batch Update Data

To improve performance, it is recommended to minimize frequent data update operations. If you need to update a large amount of data, consider merging these updates into a single batch operation to reduce unnecessary rendering times.

// 不推荐的做法:逐个更新数据点
data.forEach((item, index) => {
  setTimeout(() => {
    chart.updateSeriesData([/* 更新后的数据 */]);
  }, index * 100); // 每隔100毫秒更新一个数据点
});

// 推荐的做法:一次性批量更新所有数据
setTimeout(() => {
  const updatedData = data.map(item => /* 更新后的数据 */);
  chart.updateSeriesData(updatedData);
}, 1000); // 1秒后一次性更新所有数据    

Lazy Load Animation

For scenarios with large charts or a large number of data points, lazy loading can be used to delay the animation until user interaction or specific conditions are met. This helps improve initial loading speed and overall performance.

// 懒加载动画配置
const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [/* 大量数据数组 */],
      animationEnter: {
        type: 'lazyFadeIn',
        duration: 800,
        easing: 'easeInOutQuad',
        lazyLoad: true // 启用懒加载
      }
    }
  ]
};

// 当用户滚动到视口内时触发懒加载动画
window.addEventListener('scroll', () => {
  if (isInViewPort(chartContainer)) {
    chart.startLazyAnimations();
  }
});    

Cache Animation Results

For those animation effects with high computational cost, consider caching their results to avoid repeated calculations. For example, for complex path animations, you can pre-calculate the keyframes of the path and reuse these keyframes in subsequent renderings.

class PathAnimator {
  private cachedFrames: KeyFrame[];

  constructor(private pathData: PathData) {
    this.cachedFrames = this.computeKeyFrames(pathData);
  }

  private computeKeyFrames(data: PathData): KeyFrame[] {
    // 计算路径的关键帧并返回
  }

  public animate(element: IElement): void {
    // 使用缓存的关键帧进行动画
    this.applyCachedFrames(element);
  }
}    

Event Throttling and Debouncing

To avoid performance issues caused by frequent event triggering, you can apply throttling or debouncing techniques to event listeners. For example, when handling mouse hover events, you can limit the frequency of animation triggers.

import throttle from 'lodash/throttle';

// 对鼠标悬停事件应用节流
chart.on('element:hover', throttle((event) => {
  // 触发悬停动画
}, 200)); // 每200毫秒最多触发一次    

4. Case Study

Case: Dynamic Bar Chart

Suppose we are developing a dynamic bar chart that updates in real-time, with a new batch of data added to the chart every second. We need to ensure that each time the data is updated, the newly added data points are presented to the user in a smooth and engaging manner, while the existing data points remain stable.

Step 1: Define Basic Configuration

First, define the basic configuration of the bar chart, including initial data and other visual attributes. At the same time, specify animationEnter, animationUpdate, and animationExit configurations to ensure animations are triggered when data changes.

const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [/* 初始数据数组 */],
      animationEnter: {
        type: 'fadeIn',
        duration: 800,
        easing: 'easeInOutQuad'
      },
      animationUpdate: {
        type: 'scaleIn',
        duration: 500,
        easing: 'easeInOutQuad'
      },
      animationExit: {
        type: 'fadeOut',
        duration: 600,
        easing: 'easeInOutQuad'
      }
    }
  ]
};    

Step 2: Implement Data Update Logic

Next, implement a timer that adds a batch of new data to the chart every second and triggers the corresponding animation.

setInterval(() => {
  const newDataBatch = generateNewData(); // 生成新的数据批次
  const updatedData = [...chart.getData(), ...newDataBatch];

  // 更新图表数据并触发动画
  chart.updateSeriesData(updatedData);
}, 1000);    

Step 3: Optimize Performance

Considering that a new batch of data is added every second, it may impact performance. Therefore, we can take the following optimization measures:

  • Batch update data: Update all new data to the chart at once, instead of adding them one by one.

  • Lazy load animations: Enable lazy load animations for newly added data points, so that animations only start playing when they enter the viewport.

  • Event throttling: Apply throttling techniques to interaction events such as mouse hover to prevent unnecessary animations from being triggered frequently.

// 批量更新数据
setTimeout(() => {
  const updatedData = generateAllNewData(); // 生成所有新的数据
  chart.updateSeriesData(updatedData);
}, 1000);

// 懒加载动画配置
const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [/* 数据数组 */],
      animationEnter: {
        type: 'lazyFadeIn',
        duration: 800,
        easing: 'easeInOutQuad',
        lazyLoad: true
      }
    }
  ]
};

// 对鼠标悬停事件应用节流
chart.on('element:hover', throttle((event) => {
  // 触发悬停动画
}, 200));    

Step 4: Enhance User Experience

To make the charts more vivid and interesting, you can add additional decorative animations to the newly added data points, such as highlighting or tooltip labels. This not only enhances visual appeal but also helps users better understand the changes in the data.

// 添加高亮显示动画
const chartSpec = {
  series: [
    {
      type: 'bar',
      data: [/* 数据数组 */],
      animationEnter: {
        type: 'fadeIn',
        duration: 800,
        easing: 'easeInOutQuad',
        onEnd: (element: IElement) => {
          element.addHighlight(); // 添加高亮效果
        }
      }
    }
  ]
};

// 添加标签提示动画
chart.on('element:hover', (event) => {
  const element = event.detail.element;
  if (element) {
    element.showTooltip(); // 显示标签提示
  }
});    

5. Dynamic Control of Animation

Dynamically Adjust Animation Parameters

In some cases, you may want to dynamically adjust animation parameters such as duration, easing functions, etc., based on user input or other external factors. VChart provides flexible methods to achieve this.

// 根据用户选择动态调整动画参数
const updateAnimationParams = (seriesIndex: number, newParams: Partial<IAnimationTypeConfig>) => {
  chart.updateSeriesOptions(seriesIndex, {
    animationEnter: {
      ...chart.getSeriesOptions(seriesIndex).animationEnter,
      ...newParams
    }
  });

  // 重新应用新的动画配置
  chart.render();
};

// 用户选择更快的动画速度
updateAnimationParams(0, { duration: 500 });    

Pause and Resume Animation

This document was revised and organized by the following person

玄魂