Basic Animation

11 min read

There are two-level animations in G6:

  • GLobal animation: Transform the graph animatively when the changes are global;
  • Item animation: The animation on a node or an edge.

## Global Animation The global animation is controlled by Graph instance. It takes effect when some global changes happen, such as:
  • graph.updateLayout(cfg) change the layout;
  • graph.changeData() change the data.

Configure animate: true when instantiating a graph to achieve it.

const graph = new G6.Graph({
  // ...                      // Other configurations
  animate: true, // Boolean, whether to activate the animation when global changes happen
  animateCfg: {
    duration: 500, // Number, the duration of one animation
    easing: 'linearEasing', // String, the easing function
  },
});

G6 supports all the easing functions in d3.js. Thus, the options of easing in animateCfg:
'easeLinear',
'easePolyIn', 'easePolyOut', 'easePolyInOut' ,
'``easeQuad``', 'easeQuadIn', 'easeQuadOut', 'easeQuadInOut' .

For more detail of the easing functions, please refer to: d3 Easings.

Item Animation

All the built-in nodes and edges are static withou animation. To animate node or edge, please register your type of Custom Node or Custom Edge, and rewrite the afterDraw function.

Node Animation

The animation frames are applied on one graphics shape of a node. We are going to introduce this part by three demos:

  • The graphics animation (Left of the figure below);
  • The background animation (Center of the figure below);
  • Partial animation (Right of the figure below).
download download download

The code of the three demos can be found at: Node Animation.

The Graphics Animation

In this example, we are going to magnify and shrink the node.

download

We first find the graphics shape to be animated by group.get('children')[0]. Here we find the 0th graphics shape of this type of node. Then, we call animate for the node to define the properties for each frame(onFrame returns the properties of each frame).

// Magnify and shrink animation
G6.registerNode(
  'circle-animate',
  {
    afterDraw(cfg, group) {
      // Get the first graphics shape of this type of node
      const shape = group.get('children')[0];
      // The animation
      shape.animate(
        {
          // Whehter play the animation repeatly
          repeat: true,
          // Returns the properties for each frame. The input parameter ratio is a number that range from 0 to 1. The return value is an object that defines the properties for this frame.
          onFrame(ratio) {
            // Magnify first, and then shrink
            const diff = ratio <= 0.5 ? ratio * 10 : (1 - ratio) * 10;
            let radius = cfg.size;
            if (isNaN(radius)) radius = radius[0];
            // The properties for this frame. Only radius for this example
            return {
              r: radius / 2 + diff,
            };
          },
        },
        3000,
        'easeCubic',
      ); // The duration of one animation is 3000, and the easing fuction is 'easeCubic'
    },
  },
  'circle',
); // This custom node extend the built-in node 'circle'. Except for the rewrited afterDraw, other functions will extend from 'circle' node

Background Animation

You can add extra shape with animation in afterDraw.

In afterDraw of this demo, we draw three background circle shape with different filling colors. And the animate is called for magnifying and fading out the three circles. We do not call onFrame here, but assign the target style for each animation to the input paramter: magify the radius to 10 and reduce the opacity to 0.1.

download
G6.registerNode(
  'background-animate',
  {
    afterDraw(cfg, group) {
      let r = cfg.size / 2;
      if (isNaN(r)) {
        r = cfg.size[0] / 2;
      }
      // The first background circle
      const back1 = group.addShape('circle', {
        zIndex: -3,
        attrs: {
          x: 0,
          y: 0,
          r,
          fill: cfg.color,
          opacity: 0.6,
        },
      });
      // The second background circle
      const back2 = group.addShape('circle', {
        zIndex: -2,
        attrs: {
          x: 0,
          y: 0,
          r,
          fill: 'blue',
          opacity: 0.6,
        },
      });
      // The third background circle
      const back3 = group.addShape('circle', {
        zIndex: -1,
        attrs: {
          x: 0,
          y: 0,
          r,
          fill: 'green',
          opacity: 0.6,
        },
      });
      group.sort(); // Sort the graphic shapes of the nodes by zIndex

      // Magnify the first circle and fade it out
      back1.animate(
        {
          r: r + 10,
          opacity: 0.1,
          repeat: true, // Play the animation repeatly
        },
        3000,
        'easeCubic',
        null,
        0,
      ); // No delay

      // Magnify the second circle and fade it out
      back2.animate(
        {
          r: r + 10,
          opacity: 0.1,
          repeat: true, // Play the animation repeatly
        },
        3000,
        'easeCubic',
        null,
        1000,
      ); // Delay 1s

      // Magnify the third circle and fade it out
      back3.animate(
        {
          r: r + 10,
          opacity: 0.1,
          repeat: true, // Play the animation repeatly
        },
        3000,
        'easeCubic',
        null,
        2000,
      ); // Delay 2s
    },
  },
  'circle',
);

Partial Animation

In this demo, we add extra graphics shape(an image) in afterDraw, and set a rotation animation for it. Note that the rotation animation is a little complicated, which should be manipulated by matrix.

download
G6.registerNode(
  'inner-animate',
  {
    afterDraw(cfg, group) {
      const size = cfg.size;
      const width = size[0] - 12;
      const height = size[1] - 12;
      // Add an image shape
      const image = group.addShape('image', {
        attrs: {
          x: -width / 2,
          y: -height / 2,
          width: width,
          height: height,
          img: cfg.img,
        },
      });
      // Add animation for the image
      image.animate(
        {
          // Play the animation repeatly
          repeat: true,
          // Returns the properties for each frame. The input parameter ratio is a number that range from 0 to 1. The return value is an object that defines the properties for this frame.
          onFrame(ratio) {
            // Rotate by manipulating matrix
            // The current matrix
            const matrix = Util.mat3.create();
            // The target matrix
            const toMatrix = Util.transform(matrix, [
              ['r', ratio * Math.PI * 2],
            ]);
            // The properties of this frame. Only target matrix for this demo
            return {
              matrix: toMatrix,
            };
          },
        },
        3000,
        'easeCubic',
      );
    },
  },
  'rect',
);

Edge Animation

We are going to introduce this part by three demos:

  • A circle move along the edge (Left of the figure below);
  • A running dashed line (Center of the figure below. The gif may look like a static edge due to the low fps problem. You can check out the demo by link);
  • A growing line (Right of the figure below).
download download download

The code of the three demo can be found in: Edge Animation.

A Moving Circle

In this demo, we add a circle shape with moving animation in afterDraw. In each frame, we return the relative position of the circle on the edge.

download
G6.registerEdge(
  'circle-running',
  {
    afterDraw(cfg, group) {
      // Get the first graphics shape of this type of edge, which is the edge's path
      const shape = group.get('children')[0];
      // The start point of the edge's path
      const startPoint = shape.getPoint(0);

      // Add a red circle shape
      const circle = group.addShape('circle', {
        attrs: {
          x: startPoint.x,
          y: startPoint.y,
          fill: 'red',
          r: 3,
        },
      });

      // Add the animation to the red circle
      circle.animate(
        {
          // Play the animation repeatly
          repeat: true,
          // Returns the properties for each frame. The input parameter ratio is a number that range from 0 to 1. The return value is an object that defines the properties for this frame
          onFrame(ratio) {
            // Get the position on the edge according to the ratio
            const tmpPoint = shape.getPoint(ratio);
            // Return the properties of this frame, x and y for this demo
            return {
              x: tmpPoint.x,
              y: tmpPoint.y,
            };
          },
        },
        3000,
      ); // The duration for one animation
    },
  },
  'cubic',
); // Extend the built-in edge cubic

Running Dashed Line

The running dashed line is achieved by modifying the lineDash in every frame.

download
// The values of the lineDash. It can be calculated by util
const dashArray = [
  [0, 1],
  [0, 2],
  [1, 2],
  [0, 1, 1, 2],
  [0, 2, 1, 2],
  [1, 2, 1, 2],
  [2, 2, 1, 2],
  [3, 2, 1, 2],
  [4, 2, 1, 2],
];

const lineDash = [4, 2, 1, 2];
const interval = 9; // The total length of the lineDash
G6.registerEdge(
  'line-dash',
  {
    afterDraw(cfg, group) {
      // Get the first graphics shape of this type of edge, which is the edge's path
      const shape = group.get('children')[0];
      // The start point of the edge's path
      const length = shape.getTotalLength();
      let totalArray = [];
      // Calculate the lineDash for the whole line
      for (var i = 0; i < length; i += interval) {
        totalArray = totalArray.concat(lineDash);
      }

      let index = 0;
      // Define the animation
      shape.animate(
        {
          // Play the animation repeatly
          repeat: true,
          // Returns the properties for each frame. The input parameter ratio is a number that range from 0 to 1. The return value is an object that defines the properties for this frame
          onFrame(ratio) {
            const cfg = {
              lineDash: dashArray[index].concat(totalArray),
            };
            // Move 1 each frame
            index = (index + 1) % interval;
            // Return the properties of this frame, lineDash for this demo
            return cfg;
          },
        },
        3000,
      ); // The duration for one animation
    },
  },
  'cubic',
); // Extend the built-in edge cubic

A Growing Edge

A growing edge can also be implemented by calculating the lineDash.

download
G6.registerEdge(
  'line-growth',
  {
    afterDraw(cfg, group) {
      const shape = group.get('children')[0];
      const length = group.getTotalLength();
      shape.animate(
        {
          // Play the animation repeatly
          repeat: true,
          // Returns the properties for each frame. The input parameter ratio is a number that range from 0 to 1. The return value is an object that defines the properties for this frame
          onFrame(ratio) {
            const startLen = ratio * length;
            // Calculate the lineDash
            const cfg = {
              lineDash: [startLen, length - startLen],
            };
            return cfg;
          },
        },
        2000,
      ); // The duration for one animation
    },
  },
  'cubic',
); // Extend the built-in edge cubic

Interaction Animation

G6 allows user to add animation for the interaction. As showin in the figure beow, when the mouse enters the node, the related edges will show the dashed line animation.
交互动画.gif
The code for the demo can be found in: Animation of State Changing.

This kind of animation is related to the State of edge. Rewrite the function setState to response the state changing. When the mouse enters a node, some state of the related edges are activated. The setState of the edges activate the animation once it receive the state changing. The steps are:

  • Rewrite the setState in custom edge, and listen to the state changing in this function;
  • Listen the mouseenter and mouseleave of the nodes to activate the state of the related edges.

The code below is a part of the code in Animation of State Changing. Please note that we have omit some code to emphasize the code related to the animation.

// const data = ...
// const graph = new G6.Graph({...});

// The values of the lineDash. It can be calculated by util
const dashArray = [
  [0, 1],
  [0, 2],
  [1, 2],
  [0, 1, 1, 2],
  [0, 2, 1, 2],
  [1, 2, 1, 2],
  [2, 2, 1, 2],
  [3, 2, 1, 2],
  [4, 2, 1, 2],
];

const lineDash = [4, 2, 1, 2];
const interval = 9; // The total length of the lineDash

// Register a type of edge named 'can-running'
G6.registerEdge(
  'can-running',
  {
    // Rewrite setState
    setState(name, value, item) {
      const shape = item.get('keyShape');
      // Response the running state
      if (name === 'running') {
        // When the running state is turned to be true
        if (value) {
          const length = shape.getTotalLength();
          let totalArray = [];
          for (var i = 0; i < length; i += interval) {
            totalArray = totalArray.concat(lineDash);
          }
          let index = 0;
          shape.animate(
            {
              // Play the animation repeatly
              repeat: true,
              // Returns the properties for each frame. The input parameter ratio is a number that range from 0 to 1. The return value is an object that defines the properties for this frame
              onFrame(ratio) {
                const cfg = {
                  lineDash: dashArray[index].concat(totalArray),
                };
                index = (index + 1) % interval;
                return cfg;
              },
            },
            3000,
          ); // The duration for one animation
        } else {
          // When the running state is turned to be false
          // Stop the animation
          shape.stopAnimate();
          // Clear the lineDash
          shape.attr('lineDash', null);
        }
      }
    },
  },
  'cubic-horizontal',
); // Extend the built-in edge cubic-horizontal

// Listen the mouseenter event on node
graph.on('node:mouseenter', ev => {
  // Get the target node of the event
  const node = ev.item;
  // Get the related edges of the target node
  const edges = node.getEdges();
  // Turn the running state of all the related edges to be true. The setState function will be activated now
  edges.forEach(edge => graph.setItemState(edge, 'running', true));
});

// Listen the mouseleave event on node
graph.on('node:mouseleave', ev => {
  // Get the target node of the event
  const node = ev.item;
  // Get the related edges of the target node
  const edges = node.getEdges();
  // Turn the running state of all the related edges to be false. The setState function will be activated now
  edges.forEach(edge => graph.setItemState(edge, 'running', false));
});

// graph.data(data);
// graph.render();

  ⚠️Attention: When running is turned to be false, the animation should be stopped and the lineDash should be cleared.