Loading...
This plugin is used to implement the Undo and Redo functions in graph editing. By recording the historical state stack of user operations, it supports backtracking or restoring operations during graph interactions. The plugin provides users with comprehensive configuration options and APIs.
The history plugin is suitable for all scenarios involving graph editing.
createGraph({data: { nodes: [{ id: 'node-1' }] },layout: { type: 'force' },node: {style: {size: 60,labelText: 'Drag Me!',labelPlacement: 'middle',labelFill: '#fff',fill: '#7e3feb',},},edge: { style: { stroke: '#8b9baf' } },behaviors: ['drag-element'],plugins: ['grid-line', { type: 'history', key: 'history' }],},{ width: 600, height: 300 },(gui, graph) => {const options = {type: 'history',stackSize: 0,};const optionFolder = gui.addFolder('History Options');optionFolder.add(options, 'type').disable(true);optionFolder.add(options, 'stackSize', 0, 10, 1);optionFolder.onChange(({ property, value }) => {graph.updatePlugin({key: 'history',[property]: value,});graph.render();});const apiFolder = gui.addFolder('History API');const instance = graph.getPluginInstance('history');apiFolder.add(instance, 'undo');apiFolder.add(instance, 'redo');apiFolder.add(instance, 'clear');},);
Add this plugin in the graph configuration:
1. Quick Configuration (Static)
Declare directly using a string. This method is simple but only supports default configurations and cannot be dynamically modified after configuration:
const graph = new Graph({// Other configurations...plugins: ['history'],});
2. Object Configuration (Recommended)
Configure using an object form, supporting custom parameters, and allowing dynamic updates at runtime:
const graph = new Graph({// Other configurations...plugins: [{type: 'history',key: 'history-1',stackSize: 10,},],});
Property | Description | Type | Default Value | Required |
---|---|---|---|---|
afterAddCommand | Called after a command is added to the Undo/Redo queue. revert is true for undo operations and false for redo operations | (cmd: Command, revert: boolean) => void | - | |
beforeAddCommand | Called before a command is added to the Undo/Redo queue. If this method returns false , the command will not be added to the queue. revert is true for undo operations and false for redo operations | (cmd: Command, revert: boolean) => boolean | void | - | |
executeCommand | Callback function when executing a command | (cmd: Command) => void | - | |
stackSize | Maximum length of history records to be recorded | number | 0 (unlimited) |
Command
// Single history commandinterface Command {current: CommandData; // Current dataoriginal: CommandData; // Original dataanimation: boolean; // Whether to enable animation}// Single history command datainterface CommandData {add: GraphData; // Added dataupdate: GraphData; // Updated dataremove: GraphData; // Removed data}// Graph datainterface GraphData {nodes?: NodeData[]; // Node dataedges?: EdgeData[]; // Edge datacombos?: ComboData[]; // Combo data}
The history plugin provides the following APIs for users to use as needed. For how to call plugin methods, please refer to the Plugin Overview Document
Determines whether a redo operation can be performed. If there are records in the redo stack, it returns true
; otherwise, it returns false
.
canRedo(): boolean;
Example:
const canRedo = historyInstance.canRedo();if (canRedo) {console.log('Redo operation can be performed');} else {console.log('Redo stack is empty, cannot redo');}
Determines whether an undo operation can be performed. If there are records in the undo stack, it returns true
; otherwise, it returns false
.
canUndo(): boolean;
Example:
const canUndo = historyInstance.canUndo();if (canUndo) {console.log('Undo operation can be performed');} else {console.log('Undo stack is empty, cannot undo');}
Clears the history records, including the undo and redo stacks.
clear(): void;
Example:
historyInstance.clear();console.log('History records cleared');
Listens to history events, allowing users to execute custom logic when specific events occur.
on(event: Loosen<HistoryEvent>, handler: (e: { cmd?: Command | null }) => void): void;
Parameter Type Description:
HistoryEvent
enum HistoryEvent {UNDO = 'undo', // When a command is undoneREDO = 'redo', // When a command is redoneCANCEL = 'cancel', // When a command is canceledADD = 'add', // When a command is added to the queueCLEAR = 'clear', // When the history queue is clearedCHANGE = 'change', // When the history queue changes}
Command
Please refer to the previous Command type description
Example:
historyInstance.on(HistoryEvent.UNDO, () => {console.log('Undo operation executed');});
Performs a redo operation and returns the plugin instance. If the redo stack is empty, no operation is performed.
redo(): History;
Example:
historyInstance.redo();console.log('Redo operation executed');
Performs an undo operation and returns the plugin instance. If the undo stack is empty, no operation is performed.
undo(): History;
Example:
historyInstance.undo();console.log('Undo operation executed');
Performs an undo operation without recording it in the history and returns the plugin instance. Note that this operation will clear the redo stack.
undoAndCancel(): History;
Example:
historyInstance.undoAndCancel();console.log('Undo and cancel operation executed');
This plugin supports two history modes:
In default mode, every time a render is triggered (for example, after updating element data, the user actively executes the graph.draw()
method to trigger rendering), the plugin records the data before and after rendering and stacks it as an operation record.
In actual needs, a user's graph editing operation may involve multiple renders. For example, in one editing operation, first display nodes A and B, then display the connection from A to B. This involves two renders (i.e., the user needs to perform graph.draw()
twice). In this scenario, the default mode will stack two history records, which are:
Obviously, in actual business, one operation should only require one undo.
But here, when undoing this operation, the user needs to call the undo
method twice, which means two undos are required.
To support such scenarios, G6 provides a batch controller (BatchController
, refer to the source code), which is provided in the graph instance context.
The history plugin implements custom operation records based on this batch controller. The code example is as follows:
const graph = new Graph({// Other configurations...plugins: [{type: 'history',key: 'history',},],});graph.context.batch.startBatch(); // Start batch operationgraph.addNodeData(...); // Display nodes A and Bgraph.draw(); // First render triggergraph.addEdgeData(...); // Display the connection from A to Bgraph.draw(); // Second render triggergraph.context.batch.endBatch(); // End batch operation
In the example:
startBatch
method of the batch controller instance, the history plugin is informed that batch operations are now being performed. Before the batch operation ends, no matter how many renders are triggered, no history records should be stacked (the history plugin will record the change data for each render trigger).endBatch()
method. The history plugin listens for the completion of the batch operation and stacks this batch operation as a history record.Finally, the user only needs to perform one undo
to undo.
Below are some common cases with corresponding code references.
In actual business scenarios, you may need to customize the toolbar of the canvas, which involves the enable and disable states of the undo and redo buttons.
const canUndo = false;const canRedo = false;const graph = new Graph({// Other configurations...plugins: [{type: 'history',key: 'history',},],});const historyInstance = graph.getPluginInstance('history');historyInstance.on(HistoryEvent.CHANGE, () => {canUndo = historyInstance.canUndo();canRedo = historyInstance.canRedo();});
In the example, by listening to the HistoryEvent.CHANGE
event, which is triggered when the history queue changes, it is determined in real-time whether undo and redo operations can be performed.
Here is a simple scenario: only the operation of removing elements is allowed to enter the history queue.
const graph = new Graph({// Other configurations...plugins: [{type: 'history',key: 'history',beforeAddCommand: (cmd) => {return (cmd.current.remove?.nodes?.length > 0 ||cmd.current.remove?.combos?.length > 0 ||cmd.current.remove?.edges?.length > 0);},},],});
In the example, the configuration option beforeAddCommand is used to determine whether there are elements removed in cmd.current.remove
.