From 9335f887742eb57fa98a2d45476917366aae9dab Mon Sep 17 00:00:00 2001 From: sbosse Date: Mon, 21 Jul 2025 23:42:17 +0200 Subject: [PATCH] Mon 21 Jul 22:43:21 CEST 2025 --- .../src/js/layout/mxRadialTreeLayout.js | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 js/ui/mxgraph/src/js/layout/mxRadialTreeLayout.js diff --git a/js/ui/mxgraph/src/js/layout/mxRadialTreeLayout.js b/js/ui/mxgraph/src/js/layout/mxRadialTreeLayout.js new file mode 100644 index 0000000..6a73894 --- /dev/null +++ b/js/ui/mxgraph/src/js/layout/mxRadialTreeLayout.js @@ -0,0 +1,317 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxRadialTreeLayout + * + * Extends to implement a radial tree algorithm. This + * layout is suitable for graphs that have no cycles (trees). Vertices that are + * not connected to the tree will be ignored by this layout. + * + * Example: + * + * (code) + * var layout = new mxRadialTreeLayout(graph); + * layout.execute(graph.getDefaultParent()); + * (end) + * + * Constructor: mxRadialTreeLayout + * + * Constructs a new radial tree layout for the specified graph + */ +function mxRadialTreeLayout(graph) +{ + mxCompactTreeLayout.call(this, graph , false); +}; + +/** + * Extends mxGraphLayout. + */ +mxUtils.extend(mxRadialTreeLayout, mxCompactTreeLayout); + +/** + * Variable: angleOffset + * + * The initial offset to compute the angle position. + */ +mxRadialTreeLayout.prototype.angleOffset = 0.5; + +/** + * Variable: rootx + * + * The X co-ordinate of the root cell + */ +mxRadialTreeLayout.prototype.rootx = 0; + +/** + * Variable: rooty + * + * The Y co-ordinate of the root cell + */ +mxRadialTreeLayout.prototype.rooty = 0; + +/** + * Variable: levelDistance + * + * Holds the levelDistance. Default is 120. + */ +mxRadialTreeLayout.prototype.levelDistance = 120; + +/** + * Variable: nodeDistance + * + * Holds the nodeDistance. Default is 10. + */ +mxRadialTreeLayout.prototype.nodeDistance = 10; + +/** + * Variable: autoRadius + * + * Specifies if the radios should be computed automatically + */ +mxRadialTreeLayout.prototype.autoRadius = false; + +/** + * Variable: sortEdges + * + * Specifies if edges should be sorted according to the order of their + * opposite terminal cell in the model. + */ +mxRadialTreeLayout.prototype.sortEdges = false; + +/** + * Variable: rowMinX + * + * Array of leftmost x coordinate of each row + */ +mxRadialTreeLayout.prototype.rowMinX = []; + +/** + * Variable: rowMaxX + * + * Array of rightmost x coordinate of each row + */ +mxRadialTreeLayout.prototype.rowMaxX = []; + +/** + * Variable: rowMinCenX + * + * Array of x coordinate of leftmost vertex of each row + */ +mxRadialTreeLayout.prototype.rowMinCenX = []; + +/** + * Variable: rowMaxCenX + * + * Array of x coordinate of rightmost vertex of each row + */ +mxRadialTreeLayout.prototype.rowMaxCenX = []; + +/** + * Variable: rowRadi + * + * Array of y deltas of each row behind root vertex, also the radius in the tree + */ +mxRadialTreeLayout.prototype.rowRadi = []; + +/** + * Variable: row + * + * Array of vertices on each row + */ +mxRadialTreeLayout.prototype.row = []; + +/** + * Function: isVertexIgnored + * + * Returns a boolean indicating if the given should be ignored as a + * vertex. This returns true if the cell has no connections. + * + * Parameters: + * + * vertex - whose ignored state should be returned. + */ +mxRadialTreeLayout.prototype.isVertexIgnored = function(vertex) +{ + return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) || + this.graph.getConnections(vertex).length == 0; +}; + +/** + * Function: execute + * + * Implements . + * + * If the parent has any connected edges, then it is used as the root of + * the tree. Else, will be used to find a suitable + * root node within the set of children of the given parent. + * + * Parameters: + * + * parent - whose children should be laid out. + * root - Optional that will be used as the root of the tree. + */ +mxRadialTreeLayout.prototype.execute = function(parent, root) +{ + this.parent = parent; + + this.useBoundingBox = false; + this.edgeRouting = false; + //this.horizontal = false; + + mxCompactTreeLayout.prototype.execute.apply(this, arguments); + + var bounds = null; + var rootBounds = this.getVertexBounds(this.root); + this.centerX = rootBounds.x + rootBounds.width / 2; + this.centerY = rootBounds.y + rootBounds.height / 2; + + // Calculate the bounds of the involved vertices directly from the values set in the compact tree + for (var vertex in this.visited) + { + var vertexBounds = this.getVertexBounds(this.visited[vertex]); + bounds = (bounds != null) ? bounds : vertexBounds.clone(); + bounds.add(vertexBounds); + } + + this.calcRowDims([this.node], 0); + + var maxLeftGrad = 0; + var maxRightGrad = 0; + + // Find the steepest left and right gradients + for (var i = 0; i < this.row.length; i++) + { + var leftGrad = (this.centerX - this.rowMinX[i] - this.nodeDistance) / this.rowRadi[i]; + var rightGrad = (this.rowMaxX[i] - this.centerX - this.nodeDistance) / this.rowRadi[i]; + + maxLeftGrad = Math.max (maxLeftGrad, leftGrad); + maxRightGrad = Math.max (maxRightGrad, rightGrad); + } + + // Extend out row so they meet the maximum gradient and convert to polar co-ords + for (var i = 0; i < this.row.length; i++) + { + var xLeftLimit = this.centerX - this.nodeDistance - maxLeftGrad * this.rowRadi[i]; + var xRightLimit = this.centerX + this.nodeDistance + maxRightGrad * this.rowRadi[i]; + var fullWidth = xRightLimit - xLeftLimit; + + for (var j = 0; j < this.row[i].length; j ++) + { + var row = this.row[i]; + var node = row[j]; + var vertexBounds = this.getVertexBounds(node.cell); + var xProportion = (vertexBounds.x + vertexBounds.width / 2 - xLeftLimit) / (fullWidth); + var theta = 2 * Math.PI * xProportion; + node.theta = theta; + } + } + + // Post-process from outside inwards to try to align parents with children + for (var i = this.row.length - 2; i >= 0; i--) + { + var row = this.row[i]; + + for (var j = 0; j < row.length; j++) + { + var node = row[j]; + var child = node.child; + var counter = 0; + var totalTheta = 0; + + while (child != null) + { + totalTheta += child.theta; + counter++; + child = child.next; + } + + if (counter > 0) + { + var averTheta = totalTheta / counter; + + if (averTheta > node.theta && j < row.length - 1) + { + var nextTheta = row[j+1].theta; + node.theta = Math.min (averTheta, nextTheta - Math.PI/10); + } + else if (averTheta < node.theta && j > 0 ) + { + var lastTheta = row[j-1].theta; + node.theta = Math.max (averTheta, lastTheta + Math.PI/10); + } + } + } + } + + // Set locations + for (var i = 0; i < this.row.length; i++) + { + for (var j = 0; j < this.row[i].length; j ++) + { + var row = this.row[i]; + var node = row[j]; + var vertexBounds = this.getVertexBounds(node.cell); + this.setVertexLocation(node.cell, + this.centerX - vertexBounds.width / 2 + this.rowRadi[i] * Math.cos(node.theta), + this.centerY - vertexBounds.height / 2 + this.rowRadi[i] * Math.sin(node.theta)); + } + } +}; + +/** + * Function: calcRowDims + * + * Recursive function to calculate the dimensions of each row + * + * Parameters: + * + * row - Array of internal nodes, the children of which are to be processed. + * rowNum - Integer indicating which row is being processed. + */ +mxRadialTreeLayout.prototype.calcRowDims = function(row, rowNum) +{ + if (row == null || row.length == 0) + { + return; + } + + // Place root's children proportionally around the first level + this.rowMinX[rowNum] = this.centerX; + this.rowMaxX[rowNum] = this.centerX; + this.rowMinCenX[rowNum] = this.centerX; + this.rowMaxCenX[rowNum] = this.centerX; + this.row[rowNum] = []; + + var rowHasChildren = false; + + for (var i = 0; i < row.length; i++) + { + var child = row[i].child; + + while (child != null) + { + var cell = child.cell; + vertexBounds = this.getVertexBounds(cell); + + this.rowMinX[rowNum] = Math.min(vertexBounds.x, this.rowMinX[rowNum]); + this.rowMaxX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width, this.rowMaxX[rowNum]); + this.rowMinCenX[rowNum] = Math.min(vertexBounds.x + vertexBounds.width / 2, this.rowMinCenX[rowNum]); + this.rowMaxCenX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width / 2, this.rowMaxCenX[rowNum]); + this.rowRadi[rowNum] = vertexBounds.y - this.getVertexBounds(this.root).y; + + if (child.child != null) + { + rowHasChildren = true; + } + this.row[rowNum].push(child); + child = child.next; + } + } + + if (rowHasChildren) + { + this.calcRowDims(this.row[rowNum], rowNum + 1); + } +};