mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
feat(auto-layout): support custom layout config (#378)
This commit is contained in:
parent
50fa5a8eba
commit
9d029bf335
@ -7,11 +7,14 @@
|
|||||||
"closebracket",
|
"closebracket",
|
||||||
"codesandbox",
|
"codesandbox",
|
||||||
"douyinfe",
|
"douyinfe",
|
||||||
|
"edgesep",
|
||||||
"flowgram",
|
"flowgram",
|
||||||
"flowgram.ai",
|
"flowgram.ai",
|
||||||
"gedit",
|
"gedit",
|
||||||
"Hoverable",
|
"Hoverable",
|
||||||
"langchain",
|
"langchain",
|
||||||
|
"marginx",
|
||||||
|
"marginy",
|
||||||
"openbracket",
|
"openbracket",
|
||||||
"rsbuild",
|
"rsbuild",
|
||||||
"rspack",
|
"rspack",
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import { definePluginCreator } from '@flowgram.ai/core';
|
import { definePluginCreator } from '@flowgram.ai/core';
|
||||||
|
|
||||||
|
import { AutoLayoutOptions } from './type';
|
||||||
import { AutoLayoutService } from './services';
|
import { AutoLayoutService } from './services';
|
||||||
|
|
||||||
export const createFreeAutoLayoutPlugin = definePluginCreator({
|
export const createFreeAutoLayoutPlugin = definePluginCreator<AutoLayoutOptions>({
|
||||||
onBind: ({ bind }) => {
|
onBind: ({ bind }) => {
|
||||||
bind(AutoLayoutService).toSelf().inSingletonScope();
|
bind(AutoLayoutService).toSelf().inSingletonScope();
|
||||||
},
|
},
|
||||||
|
onInit: (ctx, opts) => {
|
||||||
|
ctx.get(AutoLayoutService).init(opts);
|
||||||
|
},
|
||||||
|
singleton: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
export const DagreLayoutOptions = {
|
import { LayoutConfig } from './type';
|
||||||
|
|
||||||
|
export const DefaultLayoutConfig: LayoutConfig = {
|
||||||
rankdir: 'LR',
|
rankdir: 'LR',
|
||||||
|
align: undefined,
|
||||||
nodesep: 100,
|
nodesep: 100,
|
||||||
|
edgesep: 10,
|
||||||
ranksep: 100,
|
ranksep: 100,
|
||||||
|
marginx: 0,
|
||||||
|
marginy: 0,
|
||||||
|
acyclicer: undefined,
|
||||||
ranker: 'network-simplex',
|
ranker: 'network-simplex',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { Graph as DagreGraph } from '@dagrejs/graphlib';
|
|||||||
import { dagreLib } from '../dagre-lib/index';
|
import { dagreLib } from '../dagre-lib/index';
|
||||||
import { DagreNode, LayoutNode } from './type';
|
import { DagreNode, LayoutNode } from './type';
|
||||||
import { LayoutStore } from './store';
|
import { LayoutStore } from './store';
|
||||||
import { DagreLayoutOptions } from './constant';
|
|
||||||
|
|
||||||
export class DagreLayout {
|
export class DagreLayout {
|
||||||
private readonly graph: DagreGraph;
|
private readonly graph: DagreGraph;
|
||||||
@ -59,7 +58,7 @@ export class DagreLayout {
|
|||||||
private createGraph(): DagreGraph {
|
private createGraph(): DagreGraph {
|
||||||
const graph = new DagreGraph({ multigraph: true });
|
const graph = new DagreGraph({ multigraph: true });
|
||||||
graph.setDefaultEdgeLabel(() => ({}));
|
graph.setDefaultEdgeLabel(() => ({}));
|
||||||
graph.setGraph(DagreLayoutOptions);
|
graph.setGraph(this.store.config);
|
||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export { Layout } from './layout';
|
export { Layout } from './layout';
|
||||||
export type { LayoutNode, LayoutEdge, GetFollowNode, LayoutOptions } from './type';
|
export type { LayoutNode, LayoutEdge, GetFollowNode, LayoutOptions } from './type';
|
||||||
export type { LayoutStore } from './store';
|
export type { LayoutStore } from './store';
|
||||||
|
export { DefaultLayoutConfig } from './constant';
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { GetFollowNode, LayoutOptions, LayoutParams } from './type';
|
import { GetFollowNode, LayoutConfig, LayoutOptions, LayoutParams } from './type';
|
||||||
import { LayoutStore } from './store';
|
import { LayoutStore } from './store';
|
||||||
import { LayoutPosition } from './position';
|
import { LayoutPosition } from './position';
|
||||||
import { DagreLayout } from './dagre';
|
import { DagreLayout } from './dagre';
|
||||||
@ -10,8 +10,8 @@ export class Layout {
|
|||||||
|
|
||||||
private readonly _position: LayoutPosition;
|
private readonly _position: LayoutPosition;
|
||||||
|
|
||||||
constructor() {
|
constructor(config: LayoutConfig) {
|
||||||
this._store = new LayoutStore();
|
this._store = new LayoutStore(config);
|
||||||
this._layout = new DagreLayout(this._store);
|
this._layout = new DagreLayout(this._store);
|
||||||
this._position = new LayoutPosition(this._store);
|
this._position = new LayoutPosition(this._store);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
} from '@flowgram.ai/free-layout-core';
|
} from '@flowgram.ai/free-layout-core';
|
||||||
import { FlowNodeBaseType, FlowNodeTransformData } from '@flowgram.ai/document';
|
import { FlowNodeBaseType, FlowNodeTransformData } from '@flowgram.ai/document';
|
||||||
|
|
||||||
import { LayoutEdge, LayoutNode, LayoutParams } from './type';
|
import type { LayoutConfig, LayoutEdge, LayoutNode, LayoutParams } from './type';
|
||||||
|
|
||||||
interface LayoutStoreData {
|
interface LayoutStoreData {
|
||||||
nodes: Map<string, LayoutNode>;
|
nodes: Map<string, LayoutNode>;
|
||||||
@ -21,6 +21,8 @@ export class LayoutStore {
|
|||||||
|
|
||||||
private container: WorkflowNodeEntity;
|
private container: WorkflowNodeEntity;
|
||||||
|
|
||||||
|
constructor(public readonly config: LayoutConfig) {}
|
||||||
|
|
||||||
public get initialized(): boolean {
|
public get initialized(): boolean {
|
||||||
return this.init;
|
return this.init;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,6 +68,27 @@ export interface LayoutOptions {
|
|||||||
getFollowNode?: GetFollowNode;
|
getFollowNode?: GetFollowNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LayoutConfig {
|
||||||
|
/** Direction for rank nodes. Can be TB, BT, LR, or RL, where T = top, B = bottom, L = left, and R = right. */
|
||||||
|
rankdir: 'TB' | 'BT' | 'LR' | 'RL';
|
||||||
|
/** Alignment for rank nodes. Can be UL, UR, DL, or DR, where U = up, D = down, L = left, and R = right. */
|
||||||
|
align: 'UL' | 'UR' | 'DL' | 'DR' | undefined;
|
||||||
|
/** Number of pixels that separate nodes horizontally in the layout. */
|
||||||
|
nodesep: number;
|
||||||
|
/** Number of pixels that separate edges horizontally in the layout. */
|
||||||
|
edgesep: number;
|
||||||
|
/** Number of pixels that separate edges horizontally in the layout. */
|
||||||
|
ranksep: number;
|
||||||
|
/** Number of pixels to use as a margin around the left and right of the graph. */
|
||||||
|
marginx: number;
|
||||||
|
/** Number of pixels to use as a margin around the top and bottom of the graph. */
|
||||||
|
marginy: number;
|
||||||
|
/** If set to greedy, uses a greedy heuristic for finding a feedback arc set for a graph. A feedback arc set is a set of edges that can be removed to make a graph acyclic. */
|
||||||
|
acyclicer: 'greedy' | undefined;
|
||||||
|
/** Type of algorithm to assigns a rank to each node in the input graph. Possible values: network-simplex, tight-tree or longest-path */
|
||||||
|
ranker: 'network-simplex' | 'tight-tree' | 'longest-path';
|
||||||
|
}
|
||||||
|
|
||||||
export type GetFollowNode = (
|
export type GetFollowNode = (
|
||||||
node: LayoutNode,
|
node: LayoutNode,
|
||||||
context: {
|
context: {
|
||||||
|
|||||||
@ -6,12 +6,23 @@ import {
|
|||||||
WorkflowNodeLinesData,
|
WorkflowNodeLinesData,
|
||||||
} from '@flowgram.ai/free-layout-core';
|
} from '@flowgram.ai/free-layout-core';
|
||||||
|
|
||||||
import { Layout, type LayoutOptions } from './layout';
|
import { AutoLayoutOptions } from './type';
|
||||||
|
import { LayoutConfig } from './layout/type';
|
||||||
|
import { DefaultLayoutConfig, Layout, type LayoutOptions } from './layout';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class AutoLayoutService {
|
export class AutoLayoutService {
|
||||||
@inject(WorkflowDocument) private readonly document: WorkflowDocument;
|
@inject(WorkflowDocument) private readonly document: WorkflowDocument;
|
||||||
|
|
||||||
|
private layoutConfig: LayoutConfig = DefaultLayoutConfig;
|
||||||
|
|
||||||
|
public init(options: AutoLayoutOptions) {
|
||||||
|
this.layoutConfig = {
|
||||||
|
...this.layoutConfig,
|
||||||
|
...options.layoutConfig,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async layout(options: LayoutOptions = {}): Promise<void> {
|
public async layout(options: LayoutOptions = {}): Promise<void> {
|
||||||
await this.layoutNode(this.document.root, options);
|
await this.layoutNode(this.document.root, options);
|
||||||
}
|
}
|
||||||
@ -29,7 +40,7 @@ export class AutoLayoutService {
|
|||||||
// 先递归执行子节点 autoLayout
|
// 先递归执行子节点 autoLayout
|
||||||
await Promise.all(nodes.map(async (child) => this.layoutNode(child, options)));
|
await Promise.all(nodes.map(async (child) => this.layoutNode(child, options)));
|
||||||
|
|
||||||
const layout = new Layout();
|
const layout = new Layout(this.layoutConfig);
|
||||||
layout.init({ nodes, edges, container: node }, options);
|
layout.init({ nodes, edges, container: node }, options);
|
||||||
layout.layout();
|
layout.layout();
|
||||||
await layout.position();
|
await layout.position();
|
||||||
|
|||||||
5
packages/plugins/free-auto-layout-plugin/src/type.ts
Normal file
5
packages/plugins/free-auto-layout-plugin/src/type.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { LayoutConfig } from './layout/type';
|
||||||
|
|
||||||
|
export interface AutoLayoutOptions {
|
||||||
|
layoutConfig?: Partial<LayoutConfig>;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user