199 lines
5.9 KiB
TypeScript
199 lines
5.9 KiB
TypeScript
|
/**
|
||
|
* @author Martin Karkowski
|
||
|
* @email m.karkowski@zema.de
|
||
|
* @create date 2019-06-19 09:31:08
|
||
|
* @modify date 2019-06-19 09:31:08
|
||
|
* @desc [description]
|
||
|
*/
|
||
|
|
||
|
import { IUndoRedoGraph } from '../interfaces/IGraph';
|
||
|
import { IBaseNodeOptions } from '../interfaces/IBaseNodeOptions';
|
||
|
import { IBaseEdgeOptions } from '../interfaces/IBaseEdgeOptions';
|
||
|
|
||
|
function getEdgeData<N extends IBaseNodeOptions, E extends IBaseEdgeOptions>(network: IUndoRedoGraph<N,E>, from: string, to: string) {
|
||
|
for (const edge of network.edges) {
|
||
|
if (edge.from === from && edge.to === to) {
|
||
|
return edge;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @param nodeID Node ID which should be checked
|
||
|
* @param network The Network Type
|
||
|
* @param selected The Elements, which are selected to start sorting the Nodes on this Elements
|
||
|
* @param allowedEdgeTypes Edge-Types, that will be included
|
||
|
* @param layers
|
||
|
* @param considered
|
||
|
* @param layer
|
||
|
* @param maxWidth
|
||
|
* @param maxHeight
|
||
|
*/
|
||
|
function recursiveAnalyse<N extends IBaseNodeOptions, E extends IBaseEdgeOptions>(
|
||
|
nodeID: string,
|
||
|
network: IUndoRedoGraph<N,E>,
|
||
|
selected: Array<string>,
|
||
|
allowedEdgeTypes = new Array<string>(),
|
||
|
layers: {
|
||
|
[index: number]: {
|
||
|
elements: Array<
|
||
|
{
|
||
|
id: string,
|
||
|
width: number,
|
||
|
height: number,
|
||
|
}
|
||
|
>,
|
||
|
totalWidth: number,
|
||
|
totalHeight: number,
|
||
|
maxHeight: number,
|
||
|
maxWidth: number
|
||
|
}
|
||
|
|
||
|
} = {},
|
||
|
considered = new Array<string>(),
|
||
|
layer = 0,
|
||
|
spacing = 50) {
|
||
|
|
||
|
/** Get the Edges */
|
||
|
const childs = network.network.getConnectedNodes(nodeID, 'to');
|
||
|
const node = network.data.nodes.get(nodeID);
|
||
|
|
||
|
if (!layers[layer]) {
|
||
|
layers[layer] = {
|
||
|
elements: new Array<
|
||
|
{
|
||
|
id: string,
|
||
|
width: number,
|
||
|
height: number
|
||
|
}
|
||
|
>(),
|
||
|
totalWidth: 0,
|
||
|
totalHeight: 0,
|
||
|
maxHeight: Number.MIN_SAFE_INTEGER,
|
||
|
maxWidth: Number.MIN_SAFE_INTEGER
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const width = Math.max(node.width || 0, node.size || 0);
|
||
|
const height = Math.max(node.height || 0, node.size || 0);
|
||
|
|
||
|
/** Update the Layer Information */
|
||
|
layers[layer].elements.push({
|
||
|
id: nodeID,
|
||
|
width,
|
||
|
height
|
||
|
});
|
||
|
layers[layer].totalWidth += width + spacing;
|
||
|
layers[layer].totalHeight += height + spacing;
|
||
|
layers[layer].maxHeight = Math.max(layers[layer].maxHeight, height)
|
||
|
layers[layer].maxWidth = Math.max(layers[layer].maxWidth, width)
|
||
|
|
||
|
considered.push(nodeID);
|
||
|
const elementsToAdd = [];
|
||
|
|
||
|
for (const childID of childs) {
|
||
|
if (!considered.includes(childID) && !selected.includes(childID)) {
|
||
|
const edgeData = getEdgeData(network, nodeID, childID);
|
||
|
if (edgeData && ((edgeData.type && allowedEdgeTypes.length > 0 && allowedEdgeTypes.includes(edgeData.type) )|| (!edgeData.type))) {
|
||
|
considered.push(childID)
|
||
|
elementsToAdd.push(childID)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (const childID of elementsToAdd) {
|
||
|
layers = recursiveAnalyse(childID, network, selected, allowedEdgeTypes, layers, considered, layer + 1, spacing);
|
||
|
}
|
||
|
|
||
|
return layers
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @param network Network
|
||
|
* @param selected Elements to Start with
|
||
|
* @param direction The Direction of the Orientation
|
||
|
* @param allowedEdgeType Edges, which should not be considerd
|
||
|
* @param spacing Spacing between the Elements
|
||
|
*/
|
||
|
export function placeNodes<N extends IBaseNodeOptions, E extends IBaseEdgeOptions>(
|
||
|
network: IUndoRedoGraph<N,E>,
|
||
|
selected: Array<string>,
|
||
|
direction: 'LR' | 'UD' = 'UD',
|
||
|
allowedEdgeType: Array<string> = ['logic:consume:token', 'logic:produce:token'],
|
||
|
spacing = 100) {
|
||
|
|
||
|
|
||
|
/** Make Shure the Positions are stored */
|
||
|
network.disableStoringContent = true;
|
||
|
network.network.storePositions();
|
||
|
|
||
|
let considered = new Array<string>();
|
||
|
let layers: {
|
||
|
[index: number]: {
|
||
|
elements: Array<
|
||
|
{
|
||
|
id: string,
|
||
|
width: number,
|
||
|
height: number,
|
||
|
}
|
||
|
>,
|
||
|
totalWidth: number,
|
||
|
totalHeight: number,
|
||
|
maxHeight: number,
|
||
|
maxWidth: number
|
||
|
}
|
||
|
|
||
|
} = {};
|
||
|
|
||
|
for (const nodeID of selected) {
|
||
|
layers = recursiveAnalyse(nodeID, network, selected, allowedEdgeType, layers, considered, 0, spacing);
|
||
|
}
|
||
|
|
||
|
const first = layers[0] ? layers[0].elements[0] || null : null;
|
||
|
|
||
|
if (first) {
|
||
|
|
||
|
/** Extract the Starting Position */
|
||
|
const pos = network.network.getPositions([first.id])[first.id];
|
||
|
|
||
|
let layerOffset_y = 0;
|
||
|
let layerOffset_x = 0;
|
||
|
|
||
|
for (const layer of Object.getOwnPropertyNames(layers)) {
|
||
|
let inLayerOffset = 0;
|
||
|
if (layers[layer].elements.length > 1) {
|
||
|
inLayerOffset = layers[layer].elements[0].width / 2 - ((direction === 'UD' ? layers[layer].totalWidth : layers[layer].totalHeight) - spacing) / 2;
|
||
|
}
|
||
|
|
||
|
for (const element of layers[layer].elements) {
|
||
|
switch (direction) {
|
||
|
case 'UD':
|
||
|
network.network.moveNode(element.id, pos.x + inLayerOffset + layerOffset_x, pos.y + layerOffset_y);
|
||
|
inLayerOffset += element.width + spacing;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch (direction) {
|
||
|
case 'UD':
|
||
|
layerOffset_y += (direction === 'UD' ? layers[layer].maxHeight : layers[layer].maxWidth) + spacing
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
network.disableStoringContent = false;
|
||
|
network.network.storePositions();
|
||
|
network.save();
|
||
|
}
|