Skip to content

Commit

Permalink
feat: fix the preset layout
Browse files Browse the repository at this point in the history
  • Loading branch information
pomelo-nwu committed Mar 23, 2020
1 parent 70c50c8 commit 9aca83e
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 100 deletions.
61 changes: 43 additions & 18 deletions packages/graphin/src/controller/layout/defaultLayouts.ts
Expand Up @@ -5,7 +5,8 @@ import forceLayout, { ForceLayoutOptions } from '../../layout/basic/force';
import dagreLayout, { DagreLayoutOption } from '../../layout/g6/dagre';
import gridLayout, { GridLayoutOptions } from '../../layout/basic/grid';
import { RandomLayoutOptions } from '../../layout/basic/random';

import TweakLayout from '../../layout/basic/tweak';
import randomLayout from '../../layout/basic/random';
import Graphin from '../../Graphin';
import { Data, ForceSimulation, GraphinProps } from '../../types';

Expand All @@ -24,6 +25,26 @@ const defaultLayouts = (graphin: Graphin, prevProps: GraphinProps) => {
const height = graph!.get('height');

return [
{
name: 'random',
desc: '随机布局',
icon: 'random',
layout: (data: Data, options: LayoutOption): { data: Data } => {
const defaultOptions = {
/** 随机区域的bbox */
bbox: {
x: 0,
y: 0,
w: width,
h: height,
},
};
return {
data: randomLayout(data, { ...defaultOptions, ...options }) as Data,
};
},
},

{
name: 'circle',
desc: '圆形布局',
Expand Down Expand Up @@ -142,15 +163,14 @@ const defaultLayouts = (graphin: Graphin, prevProps: GraphinProps) => {
layout: (data: Data, options: LayoutOption): { data: Data; forceSimulation: ForceSimulation } => {
const defaultOptions = {
data,
/** 前置布局,默认为random */
/** 前置布局,默认为 concentric */
preset: {
name: (prevProps.layout && prevProps.layout.name) || 'concentric',
options: {},
},
/** spring stiffness 弹簧劲度系数 * */
stiffness: 200.0,
/** 默认的弹簧长度 * */
defSpringLen: 200,

/** repulsion 斥力,这里指代 库伦常量Ke */
repulsion: 200.0 * 5,
/** 向心力 */
Expand All @@ -171,23 +191,28 @@ const defaultLayouts = (graphin: Graphin, prevProps: GraphinProps) => {
};

const layouOpts = { ...defaultOptions, ...options };
let { name: presetName, options: presetOptions = {} } = layouOpts.preset;

/** 只要不是初始化空数据布局,前一次的布局为力导布局,那么设置它的前置布局为force,内部采用tweak布局 */
if (prevProps && prevProps.layout!.name === 'force') {
if (prevProps.data.nodes.length === 0) {
layouOpts.preset = {
name: 'concentric',
options: {},
};
} else {
layouOpts.preset = {
name: 'force',
options: {},
};
}
/** 特殊情况处理:前置布局为force,但是前置的数据也为空,则证明是初始化force布局,否则为正常前置force布局*/
if (presetName === 'force' && prevProps.data.nodes.length === 0) {
presetName = 'concentric';
presetOptions = {};
}
let presetData = data;
// 处理 前置布局后的数据
if (presetName === 'force') {
presetData = TweakLayout(presetData, options as ForceLayoutOptions).data;
} else {
const layouts = defaultLayouts(graphin, prevProps);
const presetLayout =
layouts.find(item => {
return item.name === presetName;
}) || layouts[5]; // concentric
presetData = presetLayout?.layout(data, presetOptions as ForceLayoutOptions).data as Data;
}

const force = forceLayout(presetData, layouOpts as ForceLayoutOptions);

const force = forceLayout(layouOpts as ForceLayoutOptions);
return {
data: force.data,
forceSimulation: force.simulation,
Expand Down
101 changes: 19 additions & 82 deletions packages/graphin/src/layout/basic/force.ts
@@ -1,98 +1,37 @@
import { LayoutOption } from '../../controller/layout/defaultLayouts';
import ForceLayout from '../force/ForceLayout';
import { optimizeDrawing, optimizeDrawingByNode } from '../../perf/optimizeDrawing';
import randomLayout, { RandomLayoutOptions } from './random';
import ConcentricLayout from './concentric';
import TweakLayout from './tweak';
import { LayoutOptionBase, Data, Node, ForceSimulation, ExtendedGraph, Graph } from '../../types';

export interface ForceLayoutOptions extends LayoutOptionBase {
/** 前置布局 */
preset?: {
name: string;
options: LayoutOption;
};
/** 是否开启动画 */
animation?: boolean;
done?: (graph: Graph) => void;
import { LayoutOptionBase, Data, Node as NodeType, ForceSimulation, ExtendedGraph, Graph } from '../../types';

import { ForceProps } from '../force/ForceLayout';
import forceWithWorker from './forceWithWorker';

export interface ForceLayoutOptions extends ForceProps, LayoutOptionBase {
isOptimization?: boolean;
/** spring stiffness 弹簧劲度系数 * */
stiffness: number;
/** 默认的弹簧长度 * */
defSpringLen: number;
/** repulsion 斥力,这里指代 库伦常量Ke */
repulsion: number;
/** 速度的减震因子,其实就是阻尼系数 */
damping: number;
/** 最小能量阈值,当粒子运动,有阻尼系数的存在,最终会将初始的能量消耗殆尽 */
minEnergyThreshold: number;
/** 最大的速度 [0,1000] */
maxSpeed: number;
/** 最大迭代数 */
MaxIterations: number; // 240, // 1000000次/(1000/60) = 60000s = 1min
}
interface Return {
data: Data;
simulation: ForceSimulation;
}

const forceLayout = (options: ForceLayoutOptions): Return => {
const forceLayout = (data: Data, options: ForceLayoutOptions): Return => {
const {
width,
height,
graph,
data,
/** force options */
preset = { name: '', options: {} },
enableWorker = false,
animation,
done = () => {},
done = (graph: any) => {},
isOptimization,
...others
} = options;

const concentricOptions = {
/** 同心圆的布局范围,默认为当前画布的宽高范围 */
boundingBox: {
x1: 0,
y1: 0,
w: width,
h: height,
},
/** 节点间的距离,默认为60 */
minNodeSpacing: 60,
/** 每层的节点度数范围 */
levelWidth: (nodes: object, maxDegree: number) => {
/** 同心圆层数 */
const levelNum = 8;
return maxDegree / levelNum;
},
};
const randomOptions = {
bbox: {
x: 0,
y: 0,
w: width,
h: height,
},
};

let sourceData = data;

if (preset.name === 'random') {
sourceData = randomLayout(data, { ...randomOptions, ...preset.options } as RandomLayoutOptions) as Data;
}
if (preset.name === 'concentric') {
sourceData = new ConcentricLayout({
...concentricOptions,
...preset.options,
data,
}).run();
}
if (preset.name === 'force') {
sourceData = TweakLayout(data, options).data;
/** Webworker solution. Otherwise, browser UI rendering is blocked */
if (enableWorker) {
return forceWithWorker(data, options);
}

/** 创建力导模拟器 */
/** 1. Create a force simulator */
const simulation = new ForceLayout({
width,
height,
Expand All @@ -106,14 +45,13 @@ const forceLayout = (options: ForceLayoutOptions): Return => {
...others,
});

// 1. 挂载数据
simulation.setData(sourceData);
// 2. Mount Data
simulation.setData(data);

// 3.启动
// 4. 自定义渲染函数
// 3. Custom rendering function
simulation.register('render', (forceData: Data) => {
try {
forceData.nodes.forEach((item: Node) => {
forceData.nodes.forEach((item: NodeType) => {
const node = graph.findById(item.id);
if (node) {
// 因为有可能画布删除了节点
Expand All @@ -126,17 +64,16 @@ const forceLayout = (options: ForceLayoutOptions): Return => {
}
});
graph.refreshPositions();
sourceData = forceData;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
});

/** 4. Start force simulator */
simulation.start();

return {
data: sourceData,
data,
simulation,
};
};
Expand Down

0 comments on commit 9aca83e

Please sign in to comment.