706 lines
29 KiB
JavaScript
706 lines
29 KiB
JavaScript
/**
|
|
* Copyright (c) 2019-2020, JGraph Ltd
|
|
*/
|
|
/**
|
|
* Class: mxOrgChartLayout
|
|
*
|
|
* Extends <mxGraphLayout> to implement organization chart layout algorithm.
|
|
* The vertices need to be connected for this layout to work, vertices
|
|
* with no connections are ignored.
|
|
*
|
|
* Example:
|
|
*
|
|
* (code)
|
|
* var layout = new mxOrgChartLayout(graph);
|
|
* layout.execute(graph.getDefaultParent());
|
|
* (end)
|
|
*
|
|
*/
|
|
function mxOrgChartLayout(graph, branchOptimizer, parentChildSpacing, siblingSpacing)
|
|
{
|
|
mxGraphLayout.call(this, graph);
|
|
this.correctY = false;
|
|
|
|
switch(parseInt(branchOptimizer))
|
|
{
|
|
case 0:
|
|
this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_LINEAR;
|
|
this.correctY = true;
|
|
break;
|
|
case 1:
|
|
this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_HANGER2;
|
|
this.correctY = true;
|
|
break;
|
|
case 3:
|
|
this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE1;
|
|
break;
|
|
case 4:
|
|
this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE2;
|
|
break;
|
|
case 5:
|
|
this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_L;
|
|
break;
|
|
case 6:
|
|
this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_R;
|
|
break;
|
|
case 7:
|
|
this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_SMART;
|
|
break;
|
|
default: //and case 2
|
|
this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_HANGER4;
|
|
this.correctY = true;
|
|
}
|
|
|
|
this.parentChildSpacing = parentChildSpacing > 0 ? parentChildSpacing : 20;
|
|
this.siblingSpacing = siblingSpacing > 0 ? siblingSpacing : 20;
|
|
};
|
|
|
|
/**
|
|
* Extends mxGraphLayout.
|
|
*/
|
|
mxOrgChartLayout.prototype = new mxGraphLayout();
|
|
mxOrgChartLayout.prototype.constructor = mxOrgChartLayout;
|
|
|
|
//Branch Optimizers
|
|
mxOrgChartLayout.prototype.BRANCH_OPT_LINEAR = 'branchOptimizerAllLinear';
|
|
mxOrgChartLayout.prototype.BRANCH_OPT_HANGER2 = 'branchOptimizerAllHanger2';
|
|
mxOrgChartLayout.prototype.BRANCH_OPT_HANGER4 = 'branchOptimizerAllHanger4';
|
|
mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE1 = 'branchOptimizerAllFishbone1';
|
|
mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE2 = 'branchOptimizerAllFishbone2';
|
|
mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_L = 'branchOptimizerAllSingleColumnLeft';
|
|
mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_R = 'branchOptimizerAllSingleColumnRight';
|
|
mxOrgChartLayout.prototype.BRANCH_OPT_SMART = 'branchOptimizerSmart';
|
|
|
|
/**
|
|
* Function: execute
|
|
*
|
|
* Implements <mxGraphLayout.execute>. This operates on all children of the
|
|
* given parent.
|
|
*/
|
|
mxOrgChartLayout.prototype.execute = function(parent)
|
|
{
|
|
this.graph.model.beginUpdate();
|
|
try
|
|
{
|
|
RPOrgChart.main(this.graph, parent, this.branchOptimizer, this.parentChildSpacing, this.siblingSpacing, this.correctY);
|
|
}
|
|
finally
|
|
{
|
|
this.graph.model.endUpdate();
|
|
}
|
|
}
|
|
|
|
Bridge.define('RPOrgChart',
|
|
{
|
|
statics: {
|
|
config: {
|
|
init: function() {
|
|
|
|
}
|
|
},
|
|
main: function (graph, parent, branchOptimizer, parentChildSpacing, siblingSpacing, correctY) {
|
|
Bridge.Console.log = console.log;
|
|
Bridge.Console.error = console.error;
|
|
Bridge.Console.debug = console.debug;
|
|
|
|
RPOrgChart.graph = graph;
|
|
RPOrgChart.parent = parent;
|
|
RPOrgChart.dx = 0;
|
|
RPOrgChart.dy = 0;
|
|
|
|
if (parent.style == 'group' && parent.geometry)
|
|
{
|
|
RPOrgChart.dx = parent.geometry.x;
|
|
RPOrgChart.dy = parent.geometry.y;
|
|
}
|
|
|
|
RPOrgChart.branchOptimizer = branchOptimizer;
|
|
RPOrgChart.correctY = correctY;
|
|
RPOrgChart.parentChildSpacing = parseInt(parentChildSpacing);
|
|
RPOrgChart.siblingSpacing = parseInt(siblingSpacing);
|
|
RPOrgChart.buildChart(true);
|
|
},
|
|
|
|
diagram: {},
|
|
dataSource: {},
|
|
|
|
buildChart: function (initData) {
|
|
if (initData) {
|
|
RPOrgChart.initDiagram();
|
|
}
|
|
RPOrgChart.positionBoxes();
|
|
},
|
|
|
|
collapseAllBoxes: function(boxContainer, isCollapsed) {
|
|
var en = boxContainer.getBoxesById().getValues().getEnumerator();
|
|
while (en.moveNext()) {
|
|
var box = en.getCurrent();
|
|
if (!box.IsSpecial) {
|
|
box.IsCollapsed = isCollapsed;
|
|
}
|
|
}
|
|
},
|
|
|
|
generateData: function ()
|
|
{
|
|
var dataSource = new OrgChart.Test.TestDataSource();
|
|
|
|
var graph = RPOrgChart.graph;
|
|
var cells = graph.getChildVertices(RPOrgChart.parent);
|
|
|
|
for (var i = 0; i < cells.length; i++)
|
|
{
|
|
var cell = cells[i];
|
|
|
|
if (cell.geometry != null && cell.vertex && cell.parent == RPOrgChart.parent) //Vertices and first level children only
|
|
{
|
|
// Find cell parent. If it has more than one parent, take first parent (should be an error?)
|
|
var parentId = null;
|
|
|
|
var incomingEdge = graph.getIncomingEdges(cell)[0];
|
|
|
|
if (incomingEdge != null && incomingEdge.source != null)
|
|
{
|
|
parentId = incomingEdge.source.id;
|
|
}
|
|
|
|
var item = new OrgChart.Test.TestDataItem();
|
|
item.Id = cell.id;
|
|
item.ParentId = parentId;
|
|
dataSource.Items.add(item.getId(), item);
|
|
}
|
|
}
|
|
|
|
return dataSource;
|
|
},
|
|
|
|
initDiagram: function () {
|
|
var dataSource = RPOrgChart.generateData();
|
|
|
|
RPOrgChart.dataSource = dataSource;
|
|
|
|
var boxContainer = new OrgChart.Layout.BoxContainer.$ctor1(dataSource);
|
|
RPOrgChart.diagram = new OrgChart.Layout.Diagram();
|
|
|
|
var diagram = RPOrgChart.diagram;
|
|
diagram.setBoxes(boxContainer);
|
|
|
|
var linearLayoutStrategy = new OrgChart.Layout.LinearLayoutStrategy();
|
|
linearLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
|
|
linearLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
linearLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("linear", linearLayoutStrategy);
|
|
|
|
var multiLineHangerLayoutStrategy = new OrgChart.Layout.MultiLineHangerLayoutStrategy();
|
|
multiLineHangerLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
|
|
multiLineHangerLayoutStrategy.MaxSiblingsPerRow = 2;
|
|
multiLineHangerLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
multiLineHangerLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("hanger2", multiLineHangerLayoutStrategy);
|
|
|
|
multiLineHangerLayoutStrategy = new OrgChart.Layout.MultiLineHangerLayoutStrategy();
|
|
multiLineHangerLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
|
|
multiLineHangerLayoutStrategy.MaxSiblingsPerRow = 4;
|
|
multiLineHangerLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
multiLineHangerLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("hanger4", multiLineHangerLayoutStrategy);
|
|
|
|
var singleColumnLayoutStrategy = new OrgChart.Layout.SingleColumnLayoutStrategy();
|
|
singleColumnLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Right;
|
|
singleColumnLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
singleColumnLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("singleColumnRight", singleColumnLayoutStrategy);
|
|
|
|
singleColumnLayoutStrategy = new OrgChart.Layout.SingleColumnLayoutStrategy();
|
|
singleColumnLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Left;
|
|
singleColumnLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
singleColumnLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("singleColumnLeft", singleColumnLayoutStrategy);
|
|
|
|
var fishboneLayoutStrategy = new OrgChart.Layout.MultiLineFishboneLayoutStrategy();
|
|
fishboneLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
|
|
fishboneLayoutStrategy.MaxGroups = 1;
|
|
fishboneLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
fishboneLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("fishbone1", fishboneLayoutStrategy);
|
|
|
|
fishboneLayoutStrategy = new OrgChart.Layout.MultiLineFishboneLayoutStrategy();
|
|
fishboneLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
|
|
fishboneLayoutStrategy.MaxGroups = 2;
|
|
fishboneLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
fishboneLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("fishbone2", fishboneLayoutStrategy);
|
|
|
|
var hstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
|
|
hstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
|
|
hstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleRowHorizontal;
|
|
hstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
hstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("hstack", hstackLayoutStrategy);
|
|
|
|
var vstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
|
|
vstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
|
|
vstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleColumnVertical;
|
|
vstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
vstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("vstack", vstackLayoutStrategy);
|
|
|
|
vstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
|
|
vstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
|
|
vstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleColumnVertical;
|
|
vstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
vstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("vstackMiddle", vstackLayoutStrategy);
|
|
|
|
vstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
|
|
vstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
|
|
vstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleColumnVertical;
|
|
vstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
vstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("vstackTop", vstackLayoutStrategy);
|
|
|
|
var assistantsLayoutStrategy = new OrgChart.Layout.FishboneAssistantsLayoutStrategy();
|
|
assistantsLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
|
|
assistantsLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
|
|
assistantsLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
|
|
diagram.LayoutSettings.LayoutStrategies.add("assistants", assistantsLayoutStrategy);
|
|
|
|
diagram.LayoutSettings.DefaultLayoutStrategyId = "vstack";
|
|
diagram.LayoutSettings.DefaultAssistantLayoutStrategyId = "assistants";
|
|
//diagram.LayoutSettings.setBranchSpacing(5);
|
|
},
|
|
|
|
getBoxLevel: function(boxContainer, box) {
|
|
var level = 0;
|
|
var obj = {};
|
|
while (box.ParentId > 0) {
|
|
if (!boxContainer.getBoxesById().tryGetValue(box.ParentId, obj)) {
|
|
break;
|
|
}
|
|
box = obj.v;
|
|
level++;
|
|
}
|
|
|
|
return level;
|
|
},
|
|
|
|
onLayoutStateChanged: function (sender, args) {
|
|
if (args.State.getCurrentOperation() === OrgChart.Layout.LayoutState.Operation.PreprocessVisualTree) {
|
|
// When layout algorithm is ready to preprocess the tree,
|
|
// we need to have box sizes ready -> hence have to render visible boxes in HTML.
|
|
// Rendering can happen at earlier time, but it's just more convenient to do it here,
|
|
// to utilize some readily available information about visual tree.
|
|
RPOrgChart.renderBoxes();
|
|
}
|
|
},
|
|
|
|
renderBoxes: function () {
|
|
var visitorFunc = function (node) {
|
|
var box = node.Element;
|
|
|
|
if (box.getIsDataBound()) {
|
|
// we're being run when nodes have already been marked as visible or hidden,
|
|
// based on IsCollapsed attribute of each Box
|
|
// so use this knowledge to prevent unnecessary rendering of invisible branches
|
|
if (node.State.IsHidden) {
|
|
return true;
|
|
}
|
|
|
|
box.Size = RPOrgChart.getBoxElementSize(box.DataId);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
RPOrgChart.diagram.getVisualTree().IterateParentFirst(visitorFunc);
|
|
},
|
|
|
|
getBranchOptimizerFunc: function () {
|
|
return RPOrgChart[RPOrgChart.branchOptimizer];
|
|
},
|
|
|
|
branchOptimizerAllLinear: function(node) {
|
|
return node.getIsAssistantRoot() ? null : "linear";
|
|
},
|
|
|
|
branchOptimizerAllHanger2: function(node) {
|
|
return node.getIsAssistantRoot() ? null : "hanger2";
|
|
},
|
|
|
|
branchOptimizerAllHanger4: function(node) {
|
|
return node.getIsAssistantRoot() ? null : "hanger4";
|
|
},
|
|
|
|
branchOptimizerAllFishbone1: function(node) {
|
|
return node.getIsAssistantRoot() ? null : "fishbone1";
|
|
},
|
|
|
|
branchOptimizerAllFishbone2: function (node) {
|
|
return node.getIsAssistantRoot() ? null : "fishbone2";
|
|
},
|
|
|
|
branchOptimizerAllSingleColumnLeft: function (node) {
|
|
return node.getIsAssistantRoot() ? null : "singleColumnRight";
|
|
},
|
|
|
|
branchOptimizerAllSingleColumnRight: function (node) {
|
|
return node.getIsAssistantRoot() ? null : "singleColumnLeft";
|
|
},
|
|
|
|
branchOptimizerStackers: function(node) {
|
|
if (node.getIsAssistantRoot()) {
|
|
return null;
|
|
}
|
|
return node.Level === 0 // this is Node for boxContainer.SystemRoot, which is not visible itself
|
|
? "vstackTop"
|
|
: node.Level === 1 // this is children of SystemRoot - they appear as roots in the diagram
|
|
? "vstackMiddle"
|
|
: "hstack";
|
|
|
|
},
|
|
|
|
branchOptimizerSmart: function(node) {
|
|
if (node.getIsAssistantRoot()) {
|
|
return null;
|
|
}
|
|
|
|
var childCount = node.getChildCount();
|
|
|
|
if (childCount <= 1) {
|
|
return "vstack";
|
|
}
|
|
|
|
var nonLeafChildren = 0;
|
|
for (var i = 0; i < childCount; i++) {
|
|
if (node.Children.getItem(i).getChildCount() > 0) {
|
|
nonLeafChildren++;
|
|
}
|
|
}
|
|
|
|
if (nonLeafChildren <= 1) {
|
|
if (childCount <= 4) {
|
|
return "vstack";
|
|
}
|
|
if (childCount <= 8) {
|
|
return "fishbone1";
|
|
}
|
|
return "fishbone2";
|
|
}
|
|
|
|
return "hanger4";
|
|
},
|
|
|
|
boxSizeFunc: function (dataId) {
|
|
// ChartLayoutAlgorithm requires this function to accept data ID
|
|
// so have to convert it to Box ID first, to get rendered visual element
|
|
var boxId = RPOrgChart.diagram.getBoxes().getBoxesByDataId().getItem(dataId).Id;
|
|
return RPOrgChart.diagram.getBoxes().getBoxesById().getItem(boxId).Size;
|
|
},
|
|
|
|
getBoxElementSize: function (boxId) {
|
|
var geo = RPOrgChart.graph.model.cells[boxId].geometry;
|
|
return new OrgChart.Layout.Size.$ctor1(geo.width, geo.height);
|
|
},
|
|
|
|
positionBoxes: function () {
|
|
var diagram = RPOrgChart.diagram;
|
|
|
|
var state = new OrgChart.Layout.LayoutState(diagram);
|
|
|
|
state.addOperationChanged(RPOrgChart.onLayoutStateChanged);
|
|
state.BoxSizeFunc = Bridge.fn.bind(this, RPOrgChart.boxSizeFunc, null, true);
|
|
state.LayoutOptimizerFunc = Bridge.fn.bind(this, RPOrgChart.getBranchOptimizerFunc(), null, true);
|
|
|
|
OrgChart.Layout.LayoutAlgorithm.Apply(state);
|
|
|
|
var diagramBoundary = OrgChart.Layout.LayoutAlgorithm.ComputeBranchVisualBoundingRect(diagram.getVisualTree());
|
|
|
|
var offsetx = -diagramBoundary.getLeft() + diagramBoundary.getTop();
|
|
|
|
var graph = RPOrgChart.graph;
|
|
var cells = graph.model.cells;
|
|
var pointsList = [];
|
|
|
|
var visitorVertexFunc = function (node)
|
|
{
|
|
if (node.State.IsHidden) {
|
|
return false;
|
|
}
|
|
|
|
var box = node.Element;
|
|
|
|
if (box.getIsDataBound()) {
|
|
var cell = cells[box.DataId];
|
|
var geo = cell.geometry.clone();
|
|
geo.x = node.State.TopLeft.X + offsetx;
|
|
geo.y = node.State.TopLeft.Y;
|
|
graph.model.setGeometry(cell, geo);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
var visitorEdgeFunc = function (node)
|
|
{
|
|
//The algorithm default is 5 px only above the node, this centers it
|
|
var yCorrection = RPOrgChart.correctY? Math.min(0, -(RPOrgChart.parentChildSpacing / 2) + 5) : 0;
|
|
// Render connectors
|
|
if (node.State.Connector != null) {
|
|
|
|
var cell = cells[node.Element.DataId];
|
|
|
|
var outgoingEdge = graph.getOutgoingEdges(cell);
|
|
|
|
var uniquePoints = {};
|
|
|
|
//Sort segments points from top to bottom or left to right + add offset
|
|
for (var ix = 0; ix < node.State.Connector.Segments.length; ix++)
|
|
{
|
|
var edge = node.State.Connector.Segments[ix];
|
|
edge.mark = 1 << ix; //TODO Support up to 31 segments. In this a limit?
|
|
edge.From.X += offsetx;
|
|
edge.To.X += offsetx;
|
|
var fx = edge.From.X, fy = edge.From.Y, tx = edge.To.X, ty = edge.To.Y;
|
|
|
|
if ((fx == tx && fy > ty) || (fy == ty && fx > tx))
|
|
{
|
|
var tmp = edge.From;
|
|
edge.From = edge.To;
|
|
edge.To = tmp;
|
|
}
|
|
}
|
|
|
|
//Collecting points including intersection of segments
|
|
for (var ix = 0; ix < node.State.Connector.Segments.length; ix++)
|
|
{
|
|
var edge = node.State.Connector.Segments[ix];
|
|
var fx = edge.From.X, fy = edge.From.Y, tx = edge.To.X, ty = edge.To.Y;
|
|
var fp = new mxPoint(fx, fy);
|
|
pointsList.push(fp);
|
|
fp.mark = edge.mark;
|
|
var up = uniquePoints[fx + ',' + fy];
|
|
|
|
if (up != null)
|
|
{
|
|
up.mark |= fp.mark;
|
|
}
|
|
else
|
|
{
|
|
uniquePoints[fx + ',' + fy] = fp;
|
|
}
|
|
|
|
var tp = new mxPoint(tx, ty);
|
|
pointsList.push(tp);
|
|
tp.mark = edge.mark;
|
|
var up = uniquePoints[tx + ',' + ty];
|
|
|
|
if (up != null)
|
|
{
|
|
up.mark |= tp.mark;
|
|
}
|
|
else
|
|
{
|
|
uniquePoints[tx + ',' + ty] = tp;
|
|
}
|
|
|
|
//Find intersections
|
|
for (var j = ix + 1; j < node.State.Connector.Segments.length; j++)
|
|
{
|
|
var e2 = node.State.Connector.Segments[j];
|
|
var fx2 = e2.From.X, fy2 = e2.From.Y, tx2 = e2.To.X, ty2 = e2.To.Y;
|
|
|
|
if (fx == tx && fy <= fy2 && ty >= fy2 && fx2 <= fx && tx2 >= fx) //Ver |_ Hor
|
|
{
|
|
var ip = new mxPoint(fx, fy2);
|
|
pointsList.push(ip);
|
|
ip.mark = edge.mark | e2.mark;
|
|
var up = uniquePoints[fx + ',' + fy2];
|
|
|
|
if (up != null)
|
|
{
|
|
up.mark |= ip.mark;
|
|
}
|
|
else
|
|
{
|
|
uniquePoints[fx + ',' + fy2] = ip;
|
|
}
|
|
}
|
|
else if (fy == ty && fx <= fx2 && tx >= fx2 && fy2 <= fy && ty2 >= fy) //Hor _| Ver
|
|
{
|
|
var ip = new mxPoint(fx2, fy);
|
|
pointsList.push(ip);
|
|
ip.mark = edge.mark | e2.mark;
|
|
var up = uniquePoints[fx2 + ',' + fy]
|
|
|
|
if (up != null)
|
|
{
|
|
up.mark |= ip.mark;
|
|
}
|
|
else
|
|
{
|
|
uniquePoints[fx2 + ',' + fy] = ip;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Sort points on y then x
|
|
var pointsArr = [];
|
|
|
|
for (var k in uniquePoints)
|
|
{
|
|
pointsArr.push(uniquePoints[k]);
|
|
}
|
|
|
|
pointsArr.sort(function(a, b)
|
|
{
|
|
var dy = a.y - b.y;
|
|
|
|
return dy == 0? a.x - b.x : dy;
|
|
});
|
|
|
|
function pointOnCell(geo, p)
|
|
{
|
|
return p.x >= geo.x && p.x <= geo.x + geo.width && p.y >= geo.y && p.y <= geo.y + geo.height;
|
|
};
|
|
|
|
function adjustEdgeGeoAndStyle(edge, edgePoints)
|
|
{
|
|
var eGeo = edge.geometry.clone();
|
|
|
|
for (var i = 0; edgePoints && i < edgePoints.length; i++)
|
|
{
|
|
if (!edgePoints[i].corrected)
|
|
{
|
|
edgePoints[i].y += yCorrection;
|
|
edgePoints[i].corrected = true
|
|
}
|
|
}
|
|
|
|
eGeo.points = edgePoints;
|
|
graph.model.setGeometry(edge, eGeo);
|
|
|
|
//Remove entry and exit points
|
|
graph.setCellStyles('entryX', null, [edge]);
|
|
graph.setCellStyles('entryY', null, [edge]);
|
|
graph.setCellStyles('exitX', null, [edge]);
|
|
graph.setCellStyles('exitY', null, [edge]);
|
|
//Set type orthogonal
|
|
graph.setCellStyles('edgeStyle', 'orthogonalEdgeStyle', [edge]);
|
|
};
|
|
|
|
var outgoingEdge = graph.getOutgoingEdges(cell);
|
|
|
|
//Simple case of a single segment. TODO Handle this case earlier
|
|
if (pointsArr.length == 2 && outgoingEdge.length == 1)
|
|
{
|
|
adjustEdgeGeoAndStyle(outgoingEdge[0], pointsArr);
|
|
}
|
|
else
|
|
{
|
|
var srcGeo = cell.geometry;
|
|
var srcP;
|
|
|
|
//Find src starting point //TODO It should be first point always?
|
|
for (var i = 0; i < pointsArr.length; i++)
|
|
{
|
|
if (pointOnCell(srcGeo, pointsArr[i]))
|
|
{
|
|
srcP = pointsArr[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
var selected;
|
|
|
|
function getNextPoint(lp)
|
|
{
|
|
for (var i = 0; i < pointsArr.length; i++)
|
|
{
|
|
var p = pointsArr[i];
|
|
if (selected[p.x + ',' + p.y]) continue;
|
|
|
|
if (p.mark & lp.mark)
|
|
{
|
|
selected[p.x + ',' + p.y] = true;
|
|
return p;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var j = 0; j < outgoingEdge.length; j++)
|
|
{
|
|
if (outgoingEdge[j].target != null)
|
|
{
|
|
selected = {};
|
|
selected[srcP.x + ',' + srcP.y] = true;
|
|
var trgGeo = outgoingEdge[j].target.geometry;
|
|
|
|
var edgePoints = [srcP];
|
|
var lp = srcP;
|
|
var safeGuard = 0;
|
|
//Is BFS better?
|
|
while (safeGuard < 1000)
|
|
{
|
|
safeGuard++;
|
|
var np = getNextPoint(lp);
|
|
|
|
//retract, then remove this point
|
|
if (np == null)
|
|
{
|
|
edgePoints.pop();
|
|
lp = edgePoints[edgePoints.length - 1];
|
|
}
|
|
else
|
|
{
|
|
edgePoints.push(np);
|
|
lp = np;
|
|
if (pointOnCell(trgGeo, np)) break;
|
|
}
|
|
}
|
|
|
|
//Remove retracted points TODO can we do it in a better way?
|
|
if (edgePoints.length > 2)
|
|
{
|
|
var spX = edgePoints[0].x;
|
|
var lpX = edgePoints[edgePoints.length - 1].x;
|
|
|
|
for (var i = edgePoints.length - 2; i > 0; i--)
|
|
{
|
|
if ((spX > lpX && edgePoints[i].x < lpX) || (spX < lpX && edgePoints[i].x < spX))
|
|
{
|
|
edgePoints.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
var eGeo = outgoingEdge[j].geometry.clone();
|
|
eGeo.points = edgePoints;
|
|
RPOrgChart.graph.model.setGeometry(outgoingEdge[j], eGeo);
|
|
|
|
//Fix edge points and style
|
|
adjustEdgeGeoAndStyle(outgoingEdge[j], edgePoints);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
diagram.getVisualTree().IterateParentFirst(visitorVertexFunc);
|
|
diagram.getVisualTree().IterateParentFirst(visitorEdgeFunc);
|
|
|
|
//Cleanup
|
|
for (var i = 0; i < pointsList.length; i++)
|
|
{
|
|
delete pointsList[i].mark;
|
|
delete pointsList[i].corrected;
|
|
}
|
|
}
|
|
|
|
}
|
|
});
|
|
|
|
Bridge.init();
|