Abstract controller classes for implementing custom layout and event handling systems. Controllers provide the foundation for extending G6-core with custom rendering, layout algorithms, and event processing capabilities.
Base class for implementing custom layout algorithms.
/**
* Abstract layout controller providing layout algorithm framework
* Must be extended to implement specific layout strategies
*/
abstract class AbstractLayout {
graph: IAbstractGraph;
destroyed: boolean;
constructor(graph: IAbstractGraph);
// Abstract methods that must be implemented
abstract getLayoutType(): string;
abstract layout(success?: () => void): boolean;
// Implemented methods
init(): void;
getType(): string;
refreshLayout(): void;
destroy(): void;
getData(): GraphData;
getNodeSize(node: INode): [number, number] | number;
}Usage Example:
import { AbstractLayout } from "@antv/g6-core";
class CustomLayout extends AbstractLayout {
getLayoutType(): string {
return 'custom-layout';
}
layout(success?: () => void): boolean {
const data = this.getData();
const nodes = data.nodes || [];
// Implement custom layout algorithm
nodes.forEach((node, index) => {
node.x = index * 100;
node.y = Math.sin(index) * 50;
});
// Apply positions to graph
nodes.forEach(nodeData => {
const node = this.graph.findById(nodeData.id);
if (node) {
node.updatePosition({ x: nodeData.x, y: nodeData.y });
}
});
if (success) success();
return true;
}
}
// Usage in graph implementation
class MyGraph extends AbstractGraph {
protected initLayoutController() {
this.layoutController = new CustomLayout(this);
}
}Base class for implementing custom event handling systems.
/**
* Abstract event controller providing event management framework
* Must be extended to implement specific event handling strategies
*/
abstract class AbstractEvent {
graph: IAbstractGraph;
destroyed: boolean;
constructor(graph: IAbstractGraph);
// Abstract methods that must be implemented
abstract initEvents(): void;
abstract destroy(): void;
}Usage Example:
import { AbstractEvent } from "@antv/g6-core";
class CustomEventController extends AbstractEvent {
private eventListeners: Map<string, Function[]> = new Map();
initEvents(): void {
const canvas = this.graph.getContainer();
// Initialize mouse events
this.bindEvent(canvas, 'mousedown', this.onMouseDown.bind(this));
this.bindEvent(canvas, 'mousemove', this.onMouseMove.bind(this));
this.bindEvent(canvas, 'mouseup', this.onMouseUp.bind(this));
// Initialize keyboard events
this.bindEvent(document, 'keydown', this.onKeyDown.bind(this));
this.bindEvent(document, 'keyup', this.onKeyUp.bind(this));
}
private bindEvent(target: EventTarget, event: string, handler: Function) {
const listeners = this.eventListeners.get(event) || [];
listeners.push(handler);
this.eventListeners.set(event, listeners);
target.addEventListener(event, handler as EventListener);
}
private onMouseDown(e: MouseEvent) {
const point = this.graph.getPointByClient(e.clientX, e.clientY);
// Handle mouse down with custom logic
console.log('Mouse down at:', point);
}
private onMouseMove(e: MouseEvent) {
const point = this.graph.getPointByClient(e.clientX, e.clientY);
// Handle mouse move with custom logic
}
private onMouseUp(e: MouseEvent) {
const point = this.graph.getPointByClient(e.clientX, e.clientY);
// Handle mouse up with custom logic
console.log('Mouse up at:', point);
}
private onKeyDown(e: KeyboardEvent) {
// Handle keyboard input
if (e.key === 'Delete') {
// Delete selected items
const selectedItems = this.graph.findAllByState('node', 'selected');
selectedItems.forEach(item => this.graph.removeItem(item));
}
}
private onKeyUp(e: KeyboardEvent) {
// Handle key release
}
destroy(): void {
// Clean up all event listeners
this.eventListeners.forEach((listeners, event) => {
listeners.forEach(listener => {
document.removeEventListener(event, listener as EventListener);
this.graph.getContainer().removeEventListener(event, listener as EventListener);
});
});
this.eventListeners.clear();
this.destroyed = true;
}
}
// Usage in graph implementation
class MyGraph extends AbstractGraph {
protected initEventController() {
this.eventController = new CustomEventController(this);
}
}Detailed methods available in layout controllers.
/**
* Initialize layout controller
* Called automatically during graph construction
*/
init(): void;
/**
* Get layout type identifier
* @returns Layout type string
*/
getType(): string;
/**
* Get layout algorithm identifier (abstract)
* Must return unique string identifying the layout algorithm
* @returns Layout algorithm identifier
*/
abstract getLayoutType(): string;
/**
* Execute layout algorithm (abstract)
* Must implement the actual layout calculation and positioning
* @param success - Callback function called on successful completion
* @returns True if layout executed successfully
*/
abstract layout(success?: () => void): boolean;
/**
* Refresh layout with current data
* Recalculates layout using existing configuration
*/
refreshLayout(): void;
/**
* Get current graph data for layout calculations
* @returns Graph data with nodes, edges, and combos
*/
getData(): GraphData;
/**
* Get node size for layout calculations
* @param node - Node instance
* @returns Node size as [width, height] or single number
*/
getNodeSize(node: INode): [number, number] | number;
/**
* Clean up layout controller resources
* Removes references and prepares for garbage collection
*/
destroy(): void;Detailed methods for event controller implementation.
/**
* Initialize event system (abstract)
* Must set up all necessary event listeners and handlers
* Called automatically during graph construction
*/
abstract initEvents(): void;
/**
* Clean up event controller (abstract)
* Must remove all event listeners and clean up resources
* Called during graph destruction
*/
abstract destroy(): void;
/**
* Check if controller has been destroyed
* @returns True if controller is destroyed
*/
isDestroyed(): boolean;getData() to get current graph datagetNodeSize() for consistent node size calculationsdestroy()class ForceDirectedLayout extends AbstractLayout {
private config = {
center: [0, 0],
nodeStrength: -30,
edgeStrength: 0.1,
iterations: 300,
alpha: 0.3,
alphaDecay: 0.01
};
getLayoutType(): string {
return 'force-directed';
}
layout(success?: () => void): boolean {
const data = this.getData();
const nodes = data.nodes || [];
const edges = data.edges || [];
// Initialize positions randomly if not set
nodes.forEach(node => {
if (node.x === undefined) node.x = Math.random() * 500;
if (node.y === undefined) node.y = Math.random() * 500;
});
// Run force simulation
for (let i = 0; i < this.config.iterations; i++) {
this.simulateForces(nodes, edges);
this.config.alpha *= this.config.alphaDecay;
}
// Apply final positions
this.applyPositions(nodes);
if (success) success();
return true;
}
private simulateForces(nodes: NodeConfig[], edges: EdgeConfig[]) {
// Implement force calculations
// - Repulsion between nodes
// - Attraction along edges
// - Center force
}
private applyPositions(nodes: NodeConfig[]) {
nodes.forEach(nodeData => {
const node = this.graph.findById(nodeData.id);
if (node) {
node.updatePosition({ x: nodeData.x!, y: nodeData.y! });
}
});
}
}class AdvancedEventController extends AbstractEvent {
private dragState: {
dragging: boolean;
target: Item | null;
startPoint: Point | null;
} = { dragging: false, target: null, startPoint: null };
initEvents(): void {
const container = this.graph.getContainer();
// Mouse events
container.addEventListener('mousedown', this.onMouseDown.bind(this));
container.addEventListener('mousemove', this.onMouseMove.bind(this));
container.addEventListener('mouseup', this.onMouseUp.bind(this));
// Touch events for mobile
container.addEventListener('touchstart', this.onTouchStart.bind(this));
container.addEventListener('touchmove', this.onTouchMove.bind(this));
container.addEventListener('touchend', this.onTouchEnd.bind(this));
// Keyboard events
document.addEventListener('keydown', this.onKeyDown.bind(this));
// Wheel events for zooming
container.addEventListener('wheel', this.onWheel.bind(this));
}
private onMouseDown(e: MouseEvent) {
const point = this.graph.getPointByClient(e.clientX, e.clientY);
const target = this.getTargetItem(point);
if (target) {
this.dragState = {
dragging: true,
target,
startPoint: point
};
// Emit custom events
this.graph.emit('item:dragstart', {
item: target,
canvasX: point.x,
canvasY: point.y,
clientX: e.clientX,
clientY: e.clientY,
x: point.x,
y: point.y,
wheelDelta: 0,
target: target.getKeyShape(),
type: 'dragstart'
} as IG6GraphEvent);
}
}
private onMouseMove(e: MouseEvent) {
const point = this.graph.getPointByClient(e.clientX, e.clientY);
if (this.dragState.dragging && this.dragState.target) {
// Update item position
this.dragState.target.updatePosition(point);
this.graph.emit('item:drag', {
item: this.dragState.target,
canvasX: point.x,
canvasY: point.y,
clientX: e.clientX,
clientY: e.clientY,
x: point.x,
y: point.y,
wheelDelta: 0,
target: this.dragState.target.getKeyShape(),
type: 'drag'
} as IG6GraphEvent);
}
}
private onMouseUp(e: MouseEvent) {
if (this.dragState.dragging && this.dragState.target) {
const point = this.graph.getPointByClient(e.clientX, e.clientY);
this.graph.emit('item:dragend', {
item: this.dragState.target,
canvasX: point.x,
canvasY: point.y,
clientX: e.clientX,
clientY: e.clientY,
x: point.x,
y: point.y,
wheelDelta: 0,
target: this.dragState.target.getKeyShape(),
type: 'dragend'
} as IG6GraphEvent);
}
this.dragState = { dragging: false, target: null, startPoint: null };
}
private getTargetItem(point: Point): Item | null {
// Find item at the given point
const nodes = this.graph.getNodes();
for (const node of nodes) {
const bbox = node.getBBox();
if (point.x >= bbox.x && point.x <= bbox.x + bbox.width &&
point.y >= bbox.y && point.y <= bbox.y + bbox.height) {
return node;
}
}
return null;
}
destroy(): void {
const container = this.graph.getContainer();
// Remove all event listeners
container.removeEventListener('mousedown', this.onMouseDown);
container.removeEventListener('mousemove', this.onMouseMove);
container.removeEventListener('mouseup', this.onMouseUp);
// ... remove other listeners
this.destroyed = true;
}
}interface IAbstractGraph {
// Graph interface methods as documented in graph-management.md
}
interface GraphData {
nodes?: NodeConfig[];
edges?: EdgeConfig[];
combos?: ComboConfig[];
}
interface NodeConfig {
id: string;
x?: number;
y?: number;
[key: string]: any;
}
interface Point {
x: number;
y: number;
}
interface IG6GraphEvent {
item: Item | null;
canvasX: number;
canvasY: number;
clientX: number;
clientY: number;
x: number;
y: number;
wheelDelta: number;
target: IShapeBase;
type: string;
}