Przeglądaj źródła

feat(auto-layout): support custom layout config (#378)

Louis Young 7 miesięcy temu
rodzic
commit
9d029bf335

+ 3 - 0
cspell.json

@@ -7,11 +7,14 @@
     "closebracket",
     "codesandbox",
     "douyinfe",
+    "edgesep",
     "flowgram",
     "flowgram.ai",
     "gedit",
     "Hoverable",
     "langchain",
+    "marginx",
+    "marginy",
     "openbracket",
     "rsbuild",
     "rspack",

+ 6 - 1
packages/plugins/free-auto-layout-plugin/src/create-auto-layout-plugin.tsx

@@ -1,9 +1,14 @@
 import { definePluginCreator } from '@flowgram.ai/core';
 
+import { AutoLayoutOptions } from './type';
 import { AutoLayoutService } from './services';
 
-export const createFreeAutoLayoutPlugin = definePluginCreator({
+export const createFreeAutoLayoutPlugin = definePluginCreator<AutoLayoutOptions>({
   onBind: ({ bind }) => {
     bind(AutoLayoutService).toSelf().inSingletonScope();
   },
+  onInit: (ctx, opts) => {
+    ctx.get(AutoLayoutService).init(opts);
+  },
+  singleton: true,
 });

+ 8 - 1
packages/plugins/free-auto-layout-plugin/src/layout/constant.ts

@@ -1,6 +1,13 @@
-export const DagreLayoutOptions = {
+import { LayoutConfig } from './type';
+
+export const DefaultLayoutConfig: LayoutConfig = {
   rankdir: 'LR',
+  align: undefined,
   nodesep: 100,
+  edgesep: 10,
   ranksep: 100,
+  marginx: 0,
+  marginy: 0,
+  acyclicer: undefined,
   ranker: 'network-simplex',
 };

+ 1 - 2
packages/plugins/free-auto-layout-plugin/src/layout/dagre.ts

@@ -4,7 +4,6 @@ import { Graph as DagreGraph } from '@dagrejs/graphlib';
 import { dagreLib } from '../dagre-lib/index';
 import { DagreNode, LayoutNode } from './type';
 import { LayoutStore } from './store';
-import { DagreLayoutOptions } from './constant';
 
 export class DagreLayout {
   private readonly graph: DagreGraph;
@@ -59,7 +58,7 @@ export class DagreLayout {
   private createGraph(): DagreGraph {
     const graph = new DagreGraph({ multigraph: true });
     graph.setDefaultEdgeLabel(() => ({}));
-    graph.setGraph(DagreLayoutOptions);
+    graph.setGraph(this.store.config);
     return graph;
   }
 

+ 1 - 0
packages/plugins/free-auto-layout-plugin/src/layout/index.ts

@@ -1,3 +1,4 @@
 export { Layout } from './layout';
 export type { LayoutNode, LayoutEdge, GetFollowNode, LayoutOptions } from './type';
 export type { LayoutStore } from './store';
+export { DefaultLayoutConfig } from './constant';

+ 3 - 3
packages/plugins/free-auto-layout-plugin/src/layout/layout.ts

@@ -1,4 +1,4 @@
-import { GetFollowNode, LayoutOptions, LayoutParams } from './type';
+import { GetFollowNode, LayoutConfig, LayoutOptions, LayoutParams } from './type';
 import { LayoutStore } from './store';
 import { LayoutPosition } from './position';
 import { DagreLayout } from './dagre';
@@ -10,8 +10,8 @@ export class Layout {
 
   private readonly _position: LayoutPosition;
 
-  constructor() {
-    this._store = new LayoutStore();
+  constructor(config: LayoutConfig) {
+    this._store = new LayoutStore(config);
     this._layout = new DagreLayout(this._store);
     this._position = new LayoutPosition(this._store);
   }

+ 3 - 1
packages/plugins/free-auto-layout-plugin/src/layout/store.ts

@@ -5,7 +5,7 @@ import {
 } from '@flowgram.ai/free-layout-core';
 import { FlowNodeBaseType, FlowNodeTransformData } from '@flowgram.ai/document';
 
-import { LayoutEdge, LayoutNode, LayoutParams } from './type';
+import type { LayoutConfig, LayoutEdge, LayoutNode, LayoutParams } from './type';
 
 interface LayoutStoreData {
   nodes: Map<string, LayoutNode>;
@@ -21,6 +21,8 @@ export class LayoutStore {
 
   private container: WorkflowNodeEntity;
 
+  constructor(public readonly config: LayoutConfig) {}
+
   public get initialized(): boolean {
     return this.init;
   }

+ 21 - 0
packages/plugins/free-auto-layout-plugin/src/layout/type.ts

@@ -68,6 +68,27 @@ export interface LayoutOptions {
   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 = (
   node: LayoutNode,
   context: {

+ 13 - 2
packages/plugins/free-auto-layout-plugin/src/services.ts

@@ -6,12 +6,23 @@ import {
   WorkflowNodeLinesData,
 } 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()
 export class AutoLayoutService {
   @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> {
     await this.layoutNode(this.document.root, options);
   }
@@ -29,7 +40,7 @@ export class AutoLayoutService {
     // 先递归执行子节点 autoLayout
     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.layout();
     await layout.position();

+ 5 - 0
packages/plugins/free-auto-layout-plugin/src/type.ts

@@ -0,0 +1,5 @@
+import { LayoutConfig } from './layout/type';
+
+export interface AutoLayoutOptions {
+  layoutConfig?: Partial<LayoutConfig>;
+}