g6/packages/site/docs/manual/new-features.en.md
2023-08-31 18:16:13 +08:00

20 KiB
Raw Blame History

title order
🎉 How to Use New Features 2

Compared to G6 v4, G6 v5 introduces new capabilities in the following areas:

  • 🎞 Visual and Animation Specification: Define styles and animations using JSON spec;
  • 📂 Information Level of Details;
  • 🎨 Simple and flexible Theme Configuration ability;
  • 🤖 Flexible and powerful Data Processing capability;
  • 🎄 Fusion of Tree Diagram and Graph structure;
  • 🏀 3D large graph support;
  • 🚀 Performance Improvements, including rendering and layout computations;
  • 🌠 Multiple Renderers that can be switched at runtime.
  • 📦 Reduced package size with support for TreeShaking.

The official version is coming soon. If these features are what you have been waiting for, you can now try the G6 5.0 Beta version! If you encounter any upgrade issues, please leave us a message on GitHub.

To support these new capabilities, there are significant Breaking Changes in G6 5.0 compared to v4, which may require some effort to upgrade. We hope that the benefits brought by the new capabilities outweigh the upgrade costs.

1. Visual and Animation Specifications

JSON Spec Definition

JSON Specification Type

In v5, we standardize the graphics of all nodes/edges/combos. Each type of element has several standardized graphic names. This standard should also be followed for custom elements. If there are additional graphics, they should be placed in otherShapes.

  • node: keyShape (main graphic), labelShape (text graphic), haloShape (background halo that appears in certain states), labelBackgroundShape (text background graphic), iconShape (icon graphic in the center of the node), badgeShapes (badge graphics around the node), anchorShapes (circle graphics representing anchor points):
  • edge: keyShape (main graphic), labelShape (text graphic), haloShape (background halo that appears in certain states), labelBackgroundShape (text background graphic):

Therefore, no matter what type of node or edge, you can configure the graphics inside it using the following method:

const graph = new Graph({
  node: {
    keyShape: {
      fill: "#f00',
      r: {
        fields: ['size'],
        formatter: model => Math.max(model.data.size[0], model.data.size[1]) / 2
      }
      // ...other styles for keyShape
    },
    labelShape: {
      // You can specify a fixed string, or use the mapping method below to map to a field in the data. Other properties can also use this mapping.
      text: {
        fields: ['name'],
        formatter: model => model.data.name
      },
      // ...other styles for labelShape
    },
    labelBackgroundShape: {
      padding: [2,2,2,2],
      fill: '#0f0'
      // ...other styles for the background graphic of labelShape
    },
    iconShape: {
      // Content can be text or an image, image takes priority
      // img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
      text: 'label',
      // ...more configurations for iconShape
    },
    anchorShapes: [
      {
        position: [0, 0.5],
        r: 2,
        fill: 'red',
      },
      {
        position: [1, 0.5],
        r: 2,
        fill: 'green',
      },
      // More anchor point graphics (drawings)
    ],
    badgeShapes: [
      {
        text: 'running',
        position: 'rightTop',
        color: 'blue',
      },
      {
        text: 'error',
        position: 'right',
        color: 'blue',
      },
      // ...more badge graphics
    ],
    otherShapes: {
      xShape: {...},
      yShape: {...},
      zShape: {...},
      // ...more additional graphics
    }
  }
})

Function Mapping Configuration

Sometimes, we need to return different style configurations based on different data. In such cases, function configuration is more flexible compared to the field+formatter method:

const graph = new Graph({
  node: (model) => {
    const { id, data } = model;
    const { size, showLabel } = data;
    // Note that the returned data structure is the complete node data type
    return {
      id,
      data: {
        ...data, // Make sure to return the original data in the original data, otherwise these data will be lost
        keyShape: {
          r: Math.max(size[0], size[1]) / 2,
          // ...
        },
        labelShape: showLabel
          ? {
              // Decide whether to display text based on a business field
              text: id,
              // ... other configurations for the text graphic
            }
          : undefined,
      },
    };
  },
});

Animation Configuration

Animation Configuration Demo

In v4, to set animations for nodes, you had to use custom nodes and configure them with the animation API of the graphics. It was also difficult to control when the animation starts and ends. In v5, animations can be defined using JSON spec. You can specify the animates field in the node / edge / combo field of the graph configuration as mentioned above:

const graph = new Graph({
  node: {
    animates: {
      buildIn: [...],
      buildOut: [...],
      update: [...],
      show: [...],
      hide: [...],
    }
  }
});

Or in the functional mapping way of node / edge / combo:

const graph = new Graph({
  node: model => {
    const { id, data } = model
    return {
      id,
      data: {
        ...data,
        // ... other style configurations
        animates: {
          buildIn: [...],
          buildOut: [...],
          update: [...],
          show: [...],
          hide: [...],
        }
      }
    }
  }
});

We have standardized five animation scenarios that occur at different times for each graphic: buildIn, buildOut, update (data/state update), show (appearance, compared to hide), hide. For each scenario, animations can be specified for different graphics, different fields, and the animation configuration and execution order can be specified. For example, the following expresses the animation of various graphics during different types of updates:

update: [
  {
    // The entire node (shapeId: 'group') animates when x and y change
    fields: ['x', 'y'],
    shapeId: 'group',
    duration: 500,
  },
  {
    // The opacity of haloShape animates with a duration of 500 when selected and active states change
    fields: ['opacity'],
    shapeId: 'haloShape',
    states: ['selected', 'active'],
    duration: 500,
  },
  // When fill and r of keyShape change at the same time, they are animated in the order specified by order, which can achieve a sequential animation effect
  {
    fields: ['fill'],
    shapeId: 'keyShape',
    order: 0,
  },
  {
    fields: ['r'],
    shapeId: 'keyShape',
    order: 1,
  },
];

2. Infomation Level of Detail

Info Level of Detail DEMO

Information layering can reduce visual distractions for complex graphs and display detailed information after zooming in. You can specify the lodStrategy field in the node / edge / combo fields of the graph configuration introduced above, as shown in the code snippet below. The levels define the zoom levels at which information layering responds, and the animateCfg configuration specifies the animation method for the graph changes caused by information layering. Then, you need to configure the lod field in different graphic style configurations to specify at which level the graphic should be displayed in levels.

const graph = new Graph({
  node: {
    lodStrategy: {
      levels: [
        { zoomRange: [0, 0.5] }, // -1
        { zoomRange: [0.5, 1], primary: true }, // 0
        { zoomRange: [1, 1.5] }, // 1
        { zoomRange: [1.5, 1] }, // 2
        { zoomRange: [2, Infinity] }, // 3
      ],
      animateCfg: {
        duration: 500,
      },
    },
    labelShape: {
      lod: 1, // Show when the zoom of the graph is greater than zoomRange[0] defined in the first level of levels, and hide when it is smaller
    },
  },
});

Or use the mapping method of node / edge / combo functions to configure:

const graph = new Graph({
  node: (model) => {
    const { id, data } = model;
    const { isImportant } = data;
    return {
      id,
      data: {
        ...data,
        // ... Other configurations
        lodStrategy: {
          levels: [
            { zoomRange: [0, 0.5] }, // -1
            { zoomRange: [0.5, 1], primary: true }, // 0
            { zoomRange: [1, 1.5] }, // 1
            { zoomRange: [1.5, 1] }, // 2
            { zoomRange: [2, Infinity] }, // 3
          ],
          animateCfg: {
            duration: 500,
          },
        },
        labelShape: {
          lod: isImportant ? -1 : 2, // You can determine in which level to display the label based on the business attribute. For example, if it is an important node, display the text in all levels; otherwise, display it after zooming in to a certain extent
        },
      },
    };
  },
});

3. Theme Configuration

Theme Config DEMO

G6 comes with built-in light and dark themes, and you can also customize them. The usage is as follows:

const graph = new Graph({
  theme: {
    type: 'spec', // Built-in theme parser
    base: 'light', // Use the light theme, set base to 'dark' for the dark theme configuration
    specification: {
      node: {
        dataTypeField: 'cluster', // Specify the field name for mapping node color
        // palette: ['#bae0ff', '#91caff', '#69b1ff', '#4096ff', '#1677ff', '#0958d9', '#003eb3', '#002c8c', '#001d66'], // Custom color palette
        // palette: { a: '#f00', b: '#0f0', c: '#00f' }, // You can specify colors for special field values
        // getStyleSets: (palette) => {
        //   // More flexible configuration, return different styles for different states
        //   const styleSetsMap = {};
        //   Object.keys(palette).forEach((dataType) => {
        //     const color = palette[dataType];
        //     styleSetsMap[dataType] = {
        //       default: {
        //         keyShape: { fill: color },
        //         labelShape: { fill: color },
        //       },
        //       state1: {
        //         keyShape: { fill: '#000' },
        //       },
        //       state2: {
        //         keyShape: { stroke: '#f00' },
        //       },
        //       state3: {
        //         keyShape: { fill: '#ff0' },
        //       },
        //     };
        //   });
        //   return styleSetsMap;
        // },
      },
      edge: {
        dataTypeField: 'cluster', // Specify the field for mapping edge color
        // ... Other configurations
      },
    },
  },
});

4. Data Processing

The format of business data varies and may not conform to the data format of G6. Sometimes, it may be necessary to calculate some fields for the data in advance, such as the degree of nodes. In this case, the data processing module of G6 v5 can be used. It is one of the eight extensions of G6 v5 and is executed before user data flows into the Graph. Multiple data processing modules can be configured and they will be executed linearly. The configuration method is as follows:

const graph = new Graph({
  // ... other graph configurations
  transforms: [
    'transform-v4-data', // built-in data processor, converts v4 data format to v5
    {
      // built-in data processor, maps the size of nodes to the 'value' field of node data, normalizes the size range to [4, 28]
      type: 'map-node-size',
      field: 'value',
      range: [4, 28],
    },
  ],
});

You can customize data processors based on your own business data format and register them with the Graph for use:

import { Graph, extend } from '@antv/g6';
const CustomDataTransform = (data, options, userGraphCore) => {
  data.nodes.forEach((node) => (node.data.cluster = node.data.bussinessState === '0' ? 'cluster1' : 'cluster2'));
  data.edges.forEach((edge) => (edge.data.keyShape = { lineWidth: edge.data.weight / 2 }));
  return data;
};
const ExtGraph = extend(Graph, {
  transforms: {
    'custom-data-transform': CustomDataTransform,
  },
});
const graph = new ExtGraph({
  // ... other graph configurations
  transforms: [
    'transform-v4-data', // built-in data processor, converts v4 data format to v5
    'custom-data-transform', // use custom data processor
  ],
});

5. Fusion of Tree Graph and Graph

Graph Data in Tree Layout DEMO

v5 introduces new features related to tree graphs:

  • The layout is compatible with Graph. Graph can specify the root node and use the minimum spanning tree to establish a tree structure before using the tree layout algorithm.
  • The interaction is compatible with Graph. Graph can also expand and collapse "subtrees", which are downstream nodes without backtracking edges.
  • Supports backtrack edges and loops.
  • Supports forests (multiple trees).

If you need to use TreeGraphData, you just need to provide a data type tag when configuring the Graph:

const graph = new Graph({
  // ... other configurations
  data: {
    type: 'treeData', // type can be 'graphData', 'treeData', 'fetch', where 'fetch' will be supported in the official version
    value: data, // when type is 'treeData', value can be TreeGraphData or TreeGraphData[] to support the drawing of forests
  },
});

The data field can provide data of type GraphData, so G6 will treat it as a normal graph and generate a tree graph structure when necessary (such as using tree layout or interaction). It is also possible to specify the type as 'treeData' and pass TreeGraphData type data to the value, in which case G6 will store the tree graph structure and convert it to normal graph data for storage.

6. 3D Large Graph

3D DEMO

G6 v5 provides the ability to render and interact with 3D large graphs. To use this feature, you need to configure renderer: 'webgl-3d' on the Graph and configure the corresponding 3D node type (currently only supports sphere-node) and 3D interactions:

import { Graph, Extensions, extend } from '@antv/g6';
const ExtGraph = extend(Graph, {
  nodes: {
    'sphere-node': Extensions.SphereNode,
  },
  behaviors: {
    'orbit-canvas-3d': Extensions.OrbitCanvas3D,
    'zoom-canvas-3d': Extensions.ZoomCanvas3D,
  },
});
const graph = new ExtGraph({
  renderer: 'webgl-3d', // can be 'canvas', 'svg', 'webgl', 'webgl-3d'
  node: {
    type: 'sphere-node',
  },
  modes: {
    defaualt: ['orbit-canvas-3d', 'zoom-canvas-3d'],
  },
  // ... other graph configurations
});

7. Performance Leap & Multiple Renderers

G6 supports 2D and 3D rendering using WebGL, which greatly improves rendering performance. Different renderers can be switched at runtime by configuring the renderer on the Graph Renderer DEMO.

const graph = new Graph({
  // ... other graph configurations
  renderer: 'canvas', // 'canvas', 'svg', 'webgl', 'webgl-3d'
});

In addition, the layout package of G6, @antv/layout, supports WASM computation. To use this feature, you need to import the specific layout algorithm from the @antv/layout-wasm package and register it with Graph using extend, and then you can use it. WASM Layout DEMO.

import { ForceLayout as ForceLayoutWASM, supportsThreads, initThreads } from '@antv/layout-wasm';
const ExtGraph = extend(Graph, {
  layouts: {
    'force-wasm': ForceLayoutWASM,
  },
});
const supported = await supportsThreads();
const threads = await initThreads(supported);
const graph = new ExtGraph({
  layout: {
    type: 'force-wasm',
    threads,
    maxIteration: 200,
  },
  // ... other graph configurations
});

8. Reduce Package Size

In G6 v5, only the most commonly used features are registered on the Graph by default. Other features need to be imported from @antv/g6 or other packages and registered on the Graph before they can be configured.

import { Graph, extend, Extensions } from '@antv/g6';
// External imported features
import { ForceLayout as ForceLayoutWASM, supportsThreads, initThreads } from '@antv/layout-wasm';
// Class CustomBehaviorClass...
// Class CustomEdge...
const ExtGraph = extend(Graph, {
  behaviors: {
    'activate-relations': Extensions.ActivateRelations, // Built-in interaction, not registered in advance
    'some-custom-behavior': CustomBehaviorClass, // Custom interaction
  },
  nodes: {
    'modelRect-node': Extensions.ModelRectNode, // Built-in modelRect node, not registered in advance
  },
  edges: {
    'custom-edge': CustomEdge, // Custom edge
  },
  layouts: {
    'force-wasm': ForceLayoutWASM,
  },
});

const supported = await supportsThreads();
const threads = await initThreads(supported);

// Instantiate the extended graph
const graph = new ExtGraph({
  // ... other configurations
  modes: {
    default: [
      'drag-node', // Default registered interaction
      'activate-relations', // Built-in interaction that was imported and registered just now
      'some-custom-behavior', // Custom interaction that was imported and registered
    ],
  },
  defaultNode: {
    type: 'modelRect-node', // Built-in node type that was imported and registered just now
  },
  defaultEdge: {
    type: 'custom-edge', // Custom edge type that was imported and registered
  },
  layout: {
    type: 'force-wasm', // Layout algorithm that was imported and registered just now
    threads,
    maxIteration: 200,
  },
});

The default registered features include:

const stdLib = {
  transforms: {
    'validate-data': ValidateData, // Data validator, executed internally by G6
    'transform-v4-data': TransformV4Data, // Transform v4 data
    'map-node-size': MapNodeSize, // Map node size to a specified field of the node
  },
  themes: {
    light: LightTheme, // Light theme
    dark: DarkTheme, // Dark theme
  },
  themeSolvers: {
    spec: SpecThemeSolver, // Default theme solver
  },
  layouts: {
    force: ForceLayout, // Force-directed layout
    grid: GridLayout, // Grid layout
    circular: CircularLayout, // Circular layout
    concentric: ConcentricLayout, // Concentric layout
    ...Hierarchy, // All tree layouts, including Dendrogram, Indented, Mindmap, CompactBox
  },
  behaviors: {
    'drag-canvas': DragCanvas, // Drag canvas
    'zoom-canvas': ZoomCanvas, // Zoom canvas
    'drag-node': DragNode, // Drag node
    'drag-combo': DragCombo, // Drag combo
    'collapse-expand-combo': CollapseExpandCombo, // Collapse/expand combo
    'collapse-expand-tree': CollapseExpandTree, // Collapse/expand subtree
    'click-select': ClickSelect, // Click to select
  },
  plugins: {
    history: History, // History stack
  },
  nodes: {
    'circle-node': CircleNode, // Circle node
    'rect-node': RectNode, // Rectangular node
  },
  edges: {
    'line-edge': LineEdge, // Straight edge
  },
  combos: {
    'circle-combo': CircleCombo, // Circle combo
    'rect-combo': RectCombo, // Rectangular combo
  },
  markers: {
    // Some commonly used icons
    collapse: (x, y, r) => {
      return [
        ['M', x - r, y],
        ['a', r, r, 0, 1, 0, r * 2, 0],
        ['a', r, r, 0, 1, 0, -r * 2, 0],
        ['M', x - r + 4, y],
        ['L', x + r - 4, y],
      ];
    },
    expand: (x, y, r) => {
      return [
        ['M', x - r, y],
        ['a', r, r, 0, 1, 0, r * 2, 0],
        ['a', r, r, 0, 1, 0, -r * 2, 0],
        ['M', x - r + 4, y],
        ['L', x - r + 2 * r - 4, y],
        ['M', x - r + r, y - r + 4],
        ['L', x, y + r - 4],
      ];
    },
    upTriangle: (x, y, r) => {
      const l1 = r * Math.cos(Math.PI / 6);
      const l2 = r * Math.sin(Math.PI / 6);
      return [['M', x - l1, y + l2], ['L', x + l1, y + l2], ['L', x, y - r], ['Z']];
    },
    downTriangle: (x, y, r) => {
      const l1 = r * Math.cos(Math.PI / 6);
      const l2 = r * Math.sin(Math.PI / 6);
      return [['M', x - l1, y - l2], ['L', x + l1, y - l2], ['L', x, y + r], ['Z']];
    },
  },
};