Loading...
G6 provides multiple built-in edge types, including line, polyline, quadratic (quadratic Bézier curve edge), cubic (cubic Bézier curve edge), cubic-horizontal, cubic-vertical, and more. These built-in edges can meet most basic scenario requirements.
However, in actual projects, you may encounter requirements that these basic edges cannot satisfy. In such cases, you need to create custom edges. Don't worry, it's simpler than you think!
In G6, a complete edge typically consists of the following parts:
key
: The main graphic of the edge, representing the primary shape of the edge, such as straight lines, polylines, etc.label
: Text label, usually used to display the name or description of the edgearrow
: Arrow, used to indicate the direction of the edgehalo
: Graphic displaying halo effects around the main graphicThere are two main ways to create custom edges:
This is the most commonly used approach. You can choose to inherit from one of the following types:
BaseEdge
- The most basic edge class, providing core edge functionalityLine
- Straight line edgePolyline
- Polyline edgeQuadratic
- Quadratic Bézier curve edgeCubic
- Cubic Bézier curve edgeCubicVertical
- Vertical cubic Bézier curve edgeCubicHorizontal
- Horizontal cubic Bézier curve edgeWhy choose this approach?
If you choose to inherit from existing edge types (recommended), you can jump directly to Create Your First Custom Edge in Three Steps to start practicing. Most users will choose this approach!
If existing edge types don't meet your requirements, you can create edges from scratch based on G's underlying graphics system.
Why choose this approach?
Custom edges built from scratch need to handle all details by themselves, including graphic rendering, event response, state changes, etc., which is more challenging to develop. You can refer directly to the source code for implementation.
Let's start with the most basic BaseEdge
to implement a custom straight line edge:
import { Graph, register, BaseEdge, ExtensionCategory } from '@antv/g6';class MyLineEdge extends BaseEdge {getKeyStyle(attributes) {return { ...super.getKeyStyle(attributes), lineWidth: 2, stroke: '#A4D3EE' };}getKeyPath(attributes) {const { sourceNode, targetNode } = this;const [x1, y1] = sourceNode.getPosition();const [x2, y2] = targetNode.getPosition();return [['M', x1, y1],['L', x2, y2],];}}register(ExtensionCategory.EDGE, 'my-line-edge', MyLineEdge);const graph = new Graph({container: 'container',height: 200,data: {nodes: [{ id: 'node1', style: { x: 100, y: 50 } },{ id: 'node2', style: { x: 300, y: 120 } },],edges: [{ source: 'node1', target: 'node2' }],},node: {style: {fill: '#7FFFD4',stroke: '#5CACEE',lineWidth: 2,},},edge: {type: 'my-line-edge',style: {zIndex: 3,},},});graph.render();
import { BaseEdge } from '@antv/g6';import type { BaseEdgeStyleProps } from '@antv/g6';class MyLineEdge extends BaseEdge {// Define edge style, can add or override default stylesprotected getKeyStyle(attributes: Required<BaseEdgeStyleProps>) {// Call parent class method to get basic style, then add custom stylesreturn { ...super.getKeyStyle(attributes), lineWidth: 2, stroke: '#A4D3EE' };}// Implement abstract method: define edge path// This is an abstract method of BaseEdge, all subclasses must implement itprotected getKeyPath(attributes) {// Get source node and target nodeconst { sourceNode, targetNode } = this;// Get node position coordinatesconst [x1, y1] = sourceNode.getPosition();const [x2, y2] = targetNode.getPosition();// Return SVG path array, defining a straight line from start to endreturn [['M', x1, y1],['L', x2, y2],];}}
getKeyStyle
: Defines the basic style of the edge, such as line width, color, etc.getKeyPath
: An abstract method in BaseEdge
that must be implemented, it defines the path shape of the edgeUse the register
method to register the edge type so that G6 can recognize your custom edge:
import { ExtensionCategory } from '@antv/g6';register(ExtensionCategory.EDGE, 'my-line-edge', MyLineEdge);
The register
method requires three parameters:
ExtensionCategory.EDGE
indicates this is an edge typemy-line-edge
is the name we give to this custom edge, which will be used in configuration laterMyLineEdge
is the edge class we just createdIn the graph configuration, use our custom edge by setting edge.type
:
const graph = new Graph({container: 'container',data: {nodes: [{ id: 'node1', style: { x: 100, y: 100 } },{ id: 'node2', style: { x: 300, y: 150 } },],edges: [{ source: 'node1', target: 'node2' }],},node: {style: {fill: '#7FFFD4',stroke: '#5CACEE',lineWidth: 2,},},edge: {type: 'my-line-edge',style: {zIndex: 3,},},});graph.render();
🎉 Congratulations! You have created your first custom edge.
G6 nodes are drawn using atomic graphic units provided by the G graphics system. Here are common graphic elements and their uses:
Graphic Element | Type | Description |
---|---|---|
Circle | circle | Suitable for representing states, avatars, circular buttons, etc. Refer to SVG's <circle> element |
Ellipse | ellipse | Similar to circle, but supports scenarios with different horizontal and vertical axes. Refer to SVG's <ellipse> element |
Image | image | Used to display icons, user avatars, LOGOs, etc. Refer to SVG's <image> element |
Line | line | Used for decoration, auxiliary connections, etc. Refer to SVG's <line> element |
Path | path | Supports complex graphics such as arrows, arcs, curves, Bézier paths, etc. The path contains a set of commands and parameters with different semantics, specific usage |
Polygon | polygon | Supports custom graphics such as pentagrams, arrows. Refer to SVG's <polygon> element |
Polyline | polyline | Multi-point polyline, suitable for complex connection structures. Refer to SVG's <polyline> element |
Rectangle | rect | Most commonly used graphic, suitable as containers, cards, buttons, and other basic structures. Refer to SVG's <rect> element |
Text | text | Displays names, descriptions, labels, and other content. Provides simple single-line/multi-line text layout capabilities, single-line supports horizontal alignment, character spacing; multi-line supports explicit line breaks and automatic wrapping, vertical alignment |
For more atomic graphics and detailed properties, please refer to Element - Shape (Optional)
All these graphics can be dynamically created or updated through upsert()
, automatically managing graphic state and lifecycle.
Before starting to customize elements, you need to understand some important properties and methods in G6 element base classes:
Property | Type | Description |
---|---|---|
shapeMap | Record<string, DisplayObject> | Mapping table of all graphics under current element |
animateMap | Record<string, IAnimation> | Mapping table of all animations under current element |
upsert(name, Ctor, style, container, hooks)
: Graphic Creation/UpdateWhen creating custom elements, you will frequently use the upsert
method. It's short for "update or insert", responsible for adding or updating graphics in elements:
upsert(key: string, Ctor: { new (...args: any[]): DisplayObject }, style: Record<string, any>, container: DisplayObject);
Parameter | Type | Description |
---|---|---|
key | string | The key of the graphic, i.e., the corresponding key in shapeMap . Built-in keys include 'key' 'label' 'halo' 'icon' 'port' 'badge' The key should not use special symbols, it will be converted to camelCase to call getXxxStyle and drawXxxShape methods (see Element Conventions) |
Ctor | { new (...args: any[]): DisplayObject } | Graphic class |
style | Record<string, any> | Graphic style |
container | DisplayObject | Container to mount the graphic |
For example, inserting a purple circle at a fixed position:
this.upsert('element-key', // Unique identifier of the element'circle', // Graphic type, such as 'rect', 'circle', etc.{ x: 100, y: 100, fill: '#a975f3' }, // Style configuration objectcontainer, // Parent container);
Why use upsert
instead of directly creating graphics through container.appendChild()
? Because:
upsert
are recorded in the node's shapeMap
, you can easily get them through this.getShape(key)
render(attributes, container)
: Main Entry Point for Rendering EdgesEvery custom edge class must implement the render(attributes, container)
method, which defines how the edge is "drawn". You can use various atomic graphics here to compose the structure you want.
render(style: Record<string, any>, container: Group): void;
Parameter | Type | Description |
---|---|---|
style | Record<string, any> | Element style |
container | Group | Container |
getShape(name)
: Get Created GraphicsSometimes, you need to modify the properties of a sub-graphic after creation, or make sub-graphics interact with each other. In this case, the getShape
method can help you get any graphic previously created through upsert
:
⚠️ Note: The order of graphics is important. If graphic B depends on the position of graphic A, you must ensure A is created first
Currently conventional element properties include:
Get element size through this.getSize()
Get edge start and end points through const [sourcePoint, targetPoint] = this.getEndpoints(attributes, false)
(simple mode - doesn't consider node shape, directly returns node center or nearest port center position)
Get edge start and end points through const [sourcePoint, targetPoint] = this.getEndpoints(attributes)
(optimized mode - default is true, considers node shape, returns connection points on node boundary)
Use Paired getXxxStyle
and drawXxxShape
Methods for Graphic Drawing
getXxxStyle
is used to get graphic styles, drawXxxShape
is used to draw graphics. Graphics created this way support automatic animation execution.
Where
Xxx
is the camelCase form of the key passed when calling the upsert method.
this.context
The following lifecycle hook functions are provided, which you can override in custom edges to execute specific logic at key moments:
Hook Function | Trigger Time | Typical Usage |
---|---|---|
onCreate | When edge creation is completed with entrance animation | Bind interaction events, initialize edge state, add external listeners |
onUpdate | When edge update is completed with update animation | Update dependent data, adjust related elements, trigger linkage effects |
onDestroy | When edge completes exit animation and is destroyed | Clean up resources, remove external listeners, execute destruction notifications |
One of the most powerful aspects of G6 element design is the ability to separate "state response" from "rendering logic".
You can define styles for each state in edge configuration:
edge: {type: 'custom-edge',style: { stroke: '#eee' },state: {selected: {stroke: '#f00',},hover: {lineWidth: 3,stroke: '#1890ff',},},}
Method to switch states:
graph.setElementState(edgeId, ['selected']);
This state will be passed to the render()
method's attributes
, and the merged result by the internal system will be automatically applied to the graphics.
You can also customize rendering logic based on state:
protected getKeyStyle(attributes: Required<BaseEdgeStyleProps>) {const style = super.getKeyStyle(attributes);// Adjust style based on stateif (attributes.states?.includes('selected')) {return {...style,stroke: '#1890ff',lineWidth: 2,shadowColor: 'rgba(24,144,255,0.2)',shadowBlur: 15,};}return style;}
import { Graph, register, BaseEdge, ExtensionCategory } from '@antv/g6';class MyPolylineEdge extends BaseEdge {getKeyPath(attributes) {const [sourcePoint, targetPoint] = this.getEndpoints(attributes);return [['M', sourcePoint[0], sourcePoint[1]],['L', targetPoint[0] / 2 + (1 / 2) * sourcePoint[0], sourcePoint[1]],['L', targetPoint[0] / 2 + (1 / 2) * sourcePoint[0], targetPoint[1]],['L', targetPoint[0], targetPoint[1]],];}}register(ExtensionCategory.EDGE, 'my-polyline-edge', MyPolylineEdge);const graph = new Graph({container: 'container',height: 200,data: {nodes: [{ id: 'node-0', style: { x: 100, y: 50, ports: [{ key: 'right', placement: [1, 0.5] }] } },{ id: 'node-1', style: { x: 250, y: 150, ports: [{ key: 'left', placement: [0, 0.5] }] } },],edges: [{ source: 'node-0', target: 'node-1' }],},edge: {type: 'my-polyline-edge',style: {startArrow: true,endArrow: true,stroke: '#F6BD16',},},behaviors: ['drag-element'],});graph.render();
import { Graph, Line, register, BaseEdge, ExtensionCategory, subStyleProps } from '@antv/g6';class LabelEdge extends Line {render(attributes, container) {super.render(attributes);this.drawEndLabel(attributes, container, 'start');this.drawEndLabel(attributes, container, 'end');}drawEndLabel(attributes, container, type) {const key = type === 'start' ? 'startLabel' : 'endLabel';const [x, y] = this.getEndpoints(attributes)[type === 'start' ? 0 : 1];const fontStyle = {x,y,dx: type === 'start' ? 15 : -15,fontSize: 16,fill: 'gray',textBaseline: 'middle',textAlign: type,};const style = subStyleProps(attributes, key);const text = style.text;this.upsert(`label-${type}`, 'text', text ? { ...fontStyle, ...style } : false, container);}}register(ExtensionCategory.EDGE, 'extra-label-edge', LabelEdge);const graph = new Graph({container: 'container',height: 200,data: {nodes: [{ id: 'node-0', style: { x: 100, y: 100 } },{ id: 'node-1', style: { x: 300, y: 100 } },],edges: [{ source: 'node-0', target: 'node-1' }],},edge: {type: 'extra-label-edge',style: {startArrow: true,endArrow: true,stroke: '#F6BD16',startLabelText: 'start',endLabelText: 'end',},},behaviors: ['drag-element'],});graph.render();