Mon 21 Jul 22:43:21 CEST 2025
This commit is contained in:
parent
c922220a8b
commit
1335e0c438
|
@ -0,0 +1,675 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2006-2015, JGraph Ltd
|
||||||
|
* Copyright (c) 2006-2015, Gaudenz Alder
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Class: mxMedianHybridCrossingReduction
|
||||||
|
*
|
||||||
|
* Sets the horizontal locations of node and edge dummy nodes on each layer.
|
||||||
|
* Uses median down and up weighings as well heuristic to straighten edges as
|
||||||
|
* far as possible.
|
||||||
|
*
|
||||||
|
* Constructor: mxMedianHybridCrossingReduction
|
||||||
|
*
|
||||||
|
* Creates a coordinate assignment.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
*
|
||||||
|
* intraCellSpacing - the minimum buffer between cells on the same rank
|
||||||
|
* interRankCellSpacing - the minimum distance between cells on adjacent ranks
|
||||||
|
* orientation - the position of the root node(s) relative to the graph
|
||||||
|
* initialX - the leftmost coordinate node placement starts at
|
||||||
|
*/
|
||||||
|
function mxMedianHybridCrossingReduction(layout)
|
||||||
|
{
|
||||||
|
this.layout = layout;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends mxMedianHybridCrossingReduction.
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype = new mxHierarchicalLayoutStage();
|
||||||
|
mxMedianHybridCrossingReduction.prototype.constructor = mxMedianHybridCrossingReduction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable: layout
|
||||||
|
*
|
||||||
|
* Reference to the enclosing <mxHierarchicalLayout>.
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.layout = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable: maxIterations
|
||||||
|
*
|
||||||
|
* The maximum number of iterations to perform whilst reducing edge
|
||||||
|
* crossings. Default is 24.
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.maxIterations = 24;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable: nestedBestRanks
|
||||||
|
*
|
||||||
|
* Stores each rank as a collection of cells in the best order found for
|
||||||
|
* each layer so far
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.nestedBestRanks = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable: currentBestCrossings
|
||||||
|
*
|
||||||
|
* The total number of crossings found in the best configuration so far
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.currentBestCrossings = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable: iterationsWithoutImprovement
|
||||||
|
*
|
||||||
|
* The total number of crossings found in the best configuration so far
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.iterationsWithoutImprovement = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable: maxNoImprovementIterations
|
||||||
|
*
|
||||||
|
* The total number of crossings found in the best configuration so far
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.maxNoImprovementIterations = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function: execute
|
||||||
|
*
|
||||||
|
* Performs a vertex ordering within ranks as described by Gansner et al
|
||||||
|
* 1993
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.execute = function(parent)
|
||||||
|
{
|
||||||
|
var model = this.layout.getModel();
|
||||||
|
|
||||||
|
// Stores initial ordering as being the best one found so far
|
||||||
|
this.nestedBestRanks = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < model.ranks.length; i++)
|
||||||
|
{
|
||||||
|
this.nestedBestRanks[i] = model.ranks[i].slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
var iterationsWithoutImprovement = 0;
|
||||||
|
var currentBestCrossings = this.calculateCrossings(model);
|
||||||
|
|
||||||
|
for (var i = 0; i < this.maxIterations &&
|
||||||
|
iterationsWithoutImprovement < this.maxNoImprovementIterations; i++)
|
||||||
|
{
|
||||||
|
this.weightedMedian(i, model);
|
||||||
|
this.transpose(i, model);
|
||||||
|
var candidateCrossings = this.calculateCrossings(model);
|
||||||
|
|
||||||
|
if (candidateCrossings < currentBestCrossings)
|
||||||
|
{
|
||||||
|
currentBestCrossings = candidateCrossings;
|
||||||
|
iterationsWithoutImprovement = 0;
|
||||||
|
|
||||||
|
// Store the current rankings as the best ones
|
||||||
|
for (var j = 0; j < this.nestedBestRanks.length; j++)
|
||||||
|
{
|
||||||
|
var rank = model.ranks[j];
|
||||||
|
|
||||||
|
for (var k = 0; k < rank.length; k++)
|
||||||
|
{
|
||||||
|
var cell = rank[k];
|
||||||
|
this.nestedBestRanks[j][cell.getGeneralPurposeVariable(j)] = cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Increase count of iterations where we haven't improved the
|
||||||
|
// layout
|
||||||
|
iterationsWithoutImprovement++;
|
||||||
|
|
||||||
|
// Restore the best values to the cells
|
||||||
|
for (var j = 0; j < this.nestedBestRanks.length; j++)
|
||||||
|
{
|
||||||
|
var rank = model.ranks[j];
|
||||||
|
|
||||||
|
for (var k = 0; k < rank.length; k++)
|
||||||
|
{
|
||||||
|
var cell = rank[k];
|
||||||
|
cell.setGeneralPurposeVariable(j, k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentBestCrossings == 0)
|
||||||
|
{
|
||||||
|
// Do nothing further
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the best rankings but in the model
|
||||||
|
var ranks = [];
|
||||||
|
var rankList = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < model.maxRank + 1; i++)
|
||||||
|
{
|
||||||
|
rankList[i] = [];
|
||||||
|
ranks[i] = rankList[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < this.nestedBestRanks.length; i++)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < this.nestedBestRanks[i].length; j++)
|
||||||
|
{
|
||||||
|
rankList[i].push(this.nestedBestRanks[i][j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model.ranks = ranks;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function: calculateCrossings
|
||||||
|
*
|
||||||
|
* Calculates the total number of edge crossing in the current graph.
|
||||||
|
* Returns the current number of edge crossings in the hierarchy graph
|
||||||
|
* model in the current candidate layout
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
*
|
||||||
|
* model - the internal model describing the hierarchy
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.calculateCrossings = function(model)
|
||||||
|
{
|
||||||
|
var numRanks = model.ranks.length;
|
||||||
|
var totalCrossings = 0;
|
||||||
|
|
||||||
|
for (var i = 1; i < numRanks; i++)
|
||||||
|
{
|
||||||
|
totalCrossings += this.calculateRankCrossing(i, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalCrossings;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function: calculateRankCrossing
|
||||||
|
*
|
||||||
|
* Calculates the number of edges crossings between the specified rank and
|
||||||
|
* the rank below it. Returns the number of edges crossings with the rank
|
||||||
|
* beneath
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
*
|
||||||
|
* i - the topmost rank of the pair ( higher rank value )
|
||||||
|
* model - the internal model describing the hierarchy
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.calculateRankCrossing = function(i, model)
|
||||||
|
{
|
||||||
|
var totalCrossings = 0;
|
||||||
|
var rank = model.ranks[i];
|
||||||
|
var previousRank = model.ranks[i - 1];
|
||||||
|
|
||||||
|
var tmpIndices = [];
|
||||||
|
|
||||||
|
// Iterate over the top rank and fill in the connection information
|
||||||
|
for (var j = 0; j < rank.length; j++)
|
||||||
|
{
|
||||||
|
var node = rank[j];
|
||||||
|
var rankPosition = node.getGeneralPurposeVariable(i);
|
||||||
|
var connectedCells = node.getPreviousLayerConnectedCells(i);
|
||||||
|
var nodeIndices = [];
|
||||||
|
|
||||||
|
for (var k = 0; k < connectedCells.length; k++)
|
||||||
|
{
|
||||||
|
var connectedNode = connectedCells[k];
|
||||||
|
var otherCellRankPosition = connectedNode.getGeneralPurposeVariable(i - 1);
|
||||||
|
nodeIndices.push(otherCellRankPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeIndices.sort(function(x, y) { return x - y; });
|
||||||
|
tmpIndices[rankPosition] = nodeIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
var indices = [];
|
||||||
|
|
||||||
|
for (var j = 0; j < tmpIndices.length; j++)
|
||||||
|
{
|
||||||
|
indices = indices.concat(tmpIndices[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstIndex = 1;
|
||||||
|
|
||||||
|
while (firstIndex < previousRank.length)
|
||||||
|
{
|
||||||
|
firstIndex <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var treeSize = 2 * firstIndex - 1;
|
||||||
|
firstIndex -= 1;
|
||||||
|
|
||||||
|
var tree = [];
|
||||||
|
|
||||||
|
for (var j = 0; j < treeSize; ++j)
|
||||||
|
{
|
||||||
|
tree[j] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = 0; j < indices.length; j++)
|
||||||
|
{
|
||||||
|
var index = indices[j];
|
||||||
|
var treeIndex = index + firstIndex;
|
||||||
|
++tree[treeIndex];
|
||||||
|
|
||||||
|
while (treeIndex > 0)
|
||||||
|
{
|
||||||
|
if (treeIndex % 2)
|
||||||
|
{
|
||||||
|
totalCrossings += tree[treeIndex + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
treeIndex = (treeIndex - 1) >> 1;
|
||||||
|
++tree[treeIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalCrossings;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function: transpose
|
||||||
|
*
|
||||||
|
* Takes each possible adjacent cell pair on each rank and checks if
|
||||||
|
* swapping them around reduces the number of crossing
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
*
|
||||||
|
* mainLoopIteration - the iteration number of the main loop
|
||||||
|
* model - the internal model describing the hierarchy
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.transpose = function(mainLoopIteration, model)
|
||||||
|
{
|
||||||
|
var improved = true;
|
||||||
|
|
||||||
|
// Track the number of iterations in case of looping
|
||||||
|
var count = 0;
|
||||||
|
var maxCount = 10;
|
||||||
|
while (improved && count++ < maxCount)
|
||||||
|
{
|
||||||
|
// On certain iterations allow allow swapping of cell pairs with
|
||||||
|
// equal edge crossings switched or not switched. This help to
|
||||||
|
// nudge a stuck layout into a lower crossing total.
|
||||||
|
var nudge = mainLoopIteration % 2 == 1 && count % 2 == 1;
|
||||||
|
improved = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < model.ranks.length; i++)
|
||||||
|
{
|
||||||
|
var rank = model.ranks[i];
|
||||||
|
var orderedCells = [];
|
||||||
|
|
||||||
|
for (var j = 0; j < rank.length; j++)
|
||||||
|
{
|
||||||
|
var cell = rank[j];
|
||||||
|
var tempRank = cell.getGeneralPurposeVariable(i);
|
||||||
|
|
||||||
|
// FIXME: Workaround to avoid negative tempRanks
|
||||||
|
if (tempRank < 0)
|
||||||
|
{
|
||||||
|
tempRank = j;
|
||||||
|
}
|
||||||
|
orderedCells[tempRank] = cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftCellAboveConnections = null;
|
||||||
|
var leftCellBelowConnections = null;
|
||||||
|
var rightCellAboveConnections = null;
|
||||||
|
var rightCellBelowConnections = null;
|
||||||
|
|
||||||
|
var leftAbovePositions = null;
|
||||||
|
var leftBelowPositions = null;
|
||||||
|
var rightAbovePositions = null;
|
||||||
|
var rightBelowPositions = null;
|
||||||
|
|
||||||
|
var leftCell = null;
|
||||||
|
var rightCell = null;
|
||||||
|
|
||||||
|
for (var j = 0; j < (rank.length - 1); j++)
|
||||||
|
{
|
||||||
|
// For each intra-rank adjacent pair of cells
|
||||||
|
// see if swapping them around would reduce the
|
||||||
|
// number of edges crossing they cause in total
|
||||||
|
// On every cell pair except the first on each rank, we
|
||||||
|
// can save processing using the previous values for the
|
||||||
|
// right cell on the new left cell
|
||||||
|
if (j == 0)
|
||||||
|
{
|
||||||
|
leftCell = orderedCells[j];
|
||||||
|
leftCellAboveConnections = leftCell
|
||||||
|
.getNextLayerConnectedCells(i);
|
||||||
|
leftCellBelowConnections = leftCell
|
||||||
|
.getPreviousLayerConnectedCells(i);
|
||||||
|
leftAbovePositions = [];
|
||||||
|
leftBelowPositions = [];
|
||||||
|
|
||||||
|
for (var k = 0; k < leftCellAboveConnections.length; k++)
|
||||||
|
{
|
||||||
|
leftAbovePositions[k] = leftCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var k = 0; k < leftCellBelowConnections.length; k++)
|
||||||
|
{
|
||||||
|
leftBelowPositions[k] = leftCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
leftCellAboveConnections = rightCellAboveConnections;
|
||||||
|
leftCellBelowConnections = rightCellBelowConnections;
|
||||||
|
leftAbovePositions = rightAbovePositions;
|
||||||
|
leftBelowPositions = rightBelowPositions;
|
||||||
|
leftCell = rightCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
rightCell = orderedCells[j + 1];
|
||||||
|
rightCellAboveConnections = rightCell
|
||||||
|
.getNextLayerConnectedCells(i);
|
||||||
|
rightCellBelowConnections = rightCell
|
||||||
|
.getPreviousLayerConnectedCells(i);
|
||||||
|
|
||||||
|
rightAbovePositions = [];
|
||||||
|
rightBelowPositions = [];
|
||||||
|
|
||||||
|
for (var k = 0; k < rightCellAboveConnections.length; k++)
|
||||||
|
{
|
||||||
|
rightAbovePositions[k] = rightCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var k = 0; k < rightCellBelowConnections.length; k++)
|
||||||
|
{
|
||||||
|
rightBelowPositions[k] = rightCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCurrentCrossings = 0;
|
||||||
|
var totalSwitchedCrossings = 0;
|
||||||
|
|
||||||
|
for (var k = 0; k < leftAbovePositions.length; k++)
|
||||||
|
{
|
||||||
|
for (var ik = 0; ik < rightAbovePositions.length; ik++)
|
||||||
|
{
|
||||||
|
if (leftAbovePositions[k] > rightAbovePositions[ik])
|
||||||
|
{
|
||||||
|
totalCurrentCrossings++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftAbovePositions[k] < rightAbovePositions[ik])
|
||||||
|
{
|
||||||
|
totalSwitchedCrossings++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var k = 0; k < leftBelowPositions.length; k++)
|
||||||
|
{
|
||||||
|
for (var ik = 0; ik < rightBelowPositions.length; ik++)
|
||||||
|
{
|
||||||
|
if (leftBelowPositions[k] > rightBelowPositions[ik])
|
||||||
|
{
|
||||||
|
totalCurrentCrossings++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftBelowPositions[k] < rightBelowPositions[ik])
|
||||||
|
{
|
||||||
|
totalSwitchedCrossings++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((totalSwitchedCrossings < totalCurrentCrossings) ||
|
||||||
|
(totalSwitchedCrossings == totalCurrentCrossings &&
|
||||||
|
nudge))
|
||||||
|
{
|
||||||
|
var temp = leftCell.getGeneralPurposeVariable(i);
|
||||||
|
leftCell.setGeneralPurposeVariable(i, rightCell
|
||||||
|
.getGeneralPurposeVariable(i));
|
||||||
|
rightCell.setGeneralPurposeVariable(i, temp);
|
||||||
|
|
||||||
|
// With this pair exchanged we have to switch all of
|
||||||
|
// values for the left cell to the right cell so the
|
||||||
|
// next iteration for this rank uses it as the left
|
||||||
|
// cell again
|
||||||
|
rightCellAboveConnections = leftCellAboveConnections;
|
||||||
|
rightCellBelowConnections = leftCellBelowConnections;
|
||||||
|
rightAbovePositions = leftAbovePositions;
|
||||||
|
rightBelowPositions = leftBelowPositions;
|
||||||
|
rightCell = leftCell;
|
||||||
|
|
||||||
|
if (!nudge)
|
||||||
|
{
|
||||||
|
// Don't count nudges as improvement or we'll end
|
||||||
|
// up stuck in two combinations and not finishing
|
||||||
|
// as early as we should
|
||||||
|
improved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function: weightedMedian
|
||||||
|
*
|
||||||
|
* Sweeps up or down the layout attempting to minimise the median placement
|
||||||
|
* of connected cells on adjacent ranks
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
*
|
||||||
|
* iteration - the iteration number of the main loop
|
||||||
|
* model - the internal model describing the hierarchy
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.weightedMedian = function(iteration, model)
|
||||||
|
{
|
||||||
|
// Reverse sweep direction each time through this method
|
||||||
|
var downwardSweep = (iteration % 2 == 0);
|
||||||
|
if (downwardSweep)
|
||||||
|
{
|
||||||
|
for (var j = model.maxRank - 1; j >= 0; j--)
|
||||||
|
{
|
||||||
|
this.medianRank(j, downwardSweep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var j = 1; j < model.maxRank; j++)
|
||||||
|
{
|
||||||
|
this.medianRank(j, downwardSweep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function: medianRank
|
||||||
|
*
|
||||||
|
* Attempts to minimise the median placement of connected cells on this rank
|
||||||
|
* and one of the adjacent ranks
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
*
|
||||||
|
* rankValue - the layer number of this rank
|
||||||
|
* downwardSweep - whether or not this is a downward sweep through the graph
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.medianRank = function(rankValue, downwardSweep)
|
||||||
|
{
|
||||||
|
var numCellsForRank = this.nestedBestRanks[rankValue].length;
|
||||||
|
var medianValues = [];
|
||||||
|
var reservedPositions = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < numCellsForRank; i++)
|
||||||
|
{
|
||||||
|
var cell = this.nestedBestRanks[rankValue][i];
|
||||||
|
var sorterEntry = new MedianCellSorter();
|
||||||
|
sorterEntry.cell = cell;
|
||||||
|
|
||||||
|
// Flip whether or not equal medians are flipped on up and down
|
||||||
|
// sweeps
|
||||||
|
// TODO re-implement some kind of nudge
|
||||||
|
// medianValues[i].nudge = !downwardSweep;
|
||||||
|
var nextLevelConnectedCells;
|
||||||
|
|
||||||
|
if (downwardSweep)
|
||||||
|
{
|
||||||
|
nextLevelConnectedCells = cell
|
||||||
|
.getNextLayerConnectedCells(rankValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextLevelConnectedCells = cell
|
||||||
|
.getPreviousLayerConnectedCells(rankValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextRankValue;
|
||||||
|
|
||||||
|
if (downwardSweep)
|
||||||
|
{
|
||||||
|
nextRankValue = rankValue + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextRankValue = rankValue - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextLevelConnectedCells != null
|
||||||
|
&& nextLevelConnectedCells.length != 0)
|
||||||
|
{
|
||||||
|
sorterEntry.medianValue = this.medianValue(
|
||||||
|
nextLevelConnectedCells, nextRankValue);
|
||||||
|
medianValues.push(sorterEntry);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Nodes with no adjacent vertices are flagged in the reserved array
|
||||||
|
// to indicate they should be left in their current position.
|
||||||
|
reservedPositions[cell.getGeneralPurposeVariable(rankValue)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
medianValues.sort(MedianCellSorter.prototype.compare);
|
||||||
|
|
||||||
|
// Set the new position of each node within the rank using
|
||||||
|
// its temp variable
|
||||||
|
for (var i = 0; i < numCellsForRank; i++)
|
||||||
|
{
|
||||||
|
if (reservedPositions[i] == null)
|
||||||
|
{
|
||||||
|
var cell = medianValues.shift().cell;
|
||||||
|
cell.setGeneralPurposeVariable(rankValue, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function: medianValue
|
||||||
|
*
|
||||||
|
* Calculates the median rank order positioning for the specified cell using
|
||||||
|
* the connected cells on the specified rank. Returns the median rank
|
||||||
|
* ordering value of the connected cells
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
*
|
||||||
|
* connectedCells - the cells on the specified rank connected to the
|
||||||
|
* specified cell
|
||||||
|
* rankValue - the rank that the connected cell lie upon
|
||||||
|
*/
|
||||||
|
mxMedianHybridCrossingReduction.prototype.medianValue = function(connectedCells, rankValue)
|
||||||
|
{
|
||||||
|
var medianValues = [];
|
||||||
|
var arrayCount = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < connectedCells.length; i++)
|
||||||
|
{
|
||||||
|
var cell = connectedCells[i];
|
||||||
|
medianValues[arrayCount++] = cell.getGeneralPurposeVariable(rankValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort() sorts lexicographically by default (i.e. 11 before 9) so force
|
||||||
|
// numerical order sort
|
||||||
|
medianValues.sort(function(a,b){return a - b;});
|
||||||
|
|
||||||
|
if (arrayCount % 2 == 1)
|
||||||
|
{
|
||||||
|
// For odd numbers of adjacent vertices return the median
|
||||||
|
return medianValues[Math.floor(arrayCount / 2)];
|
||||||
|
}
|
||||||
|
else if (arrayCount == 2)
|
||||||
|
{
|
||||||
|
return ((medianValues[0] + medianValues[1]) / 2.0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var medianPoint = arrayCount / 2;
|
||||||
|
var leftMedian = medianValues[medianPoint - 1] - medianValues[0];
|
||||||
|
var rightMedian = medianValues[arrayCount - 1]
|
||||||
|
- medianValues[medianPoint];
|
||||||
|
|
||||||
|
return (medianValues[medianPoint - 1] * rightMedian + medianValues[medianPoint]
|
||||||
|
* leftMedian)
|
||||||
|
/ (leftMedian + rightMedian);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class: MedianCellSorter
|
||||||
|
*
|
||||||
|
* A utility class used to track cells whilst sorting occurs on the median
|
||||||
|
* values. Does not violate (x.compareTo(y)==0) == (x.equals(y))
|
||||||
|
*
|
||||||
|
* Constructor: MedianCellSorter
|
||||||
|
*
|
||||||
|
* Constructs a new median cell sorter.
|
||||||
|
*/
|
||||||
|
function MedianCellSorter()
|
||||||
|
{
|
||||||
|
// empty
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable: medianValue
|
||||||
|
*
|
||||||
|
* The weighted value of the cell stored.
|
||||||
|
*/
|
||||||
|
MedianCellSorter.prototype.medianValue = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variable: cell
|
||||||
|
*
|
||||||
|
* The cell whose median value is being calculated
|
||||||
|
*/
|
||||||
|
MedianCellSorter.prototype.cell = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function: compare
|
||||||
|
*
|
||||||
|
* Compares two MedianCellSorters.
|
||||||
|
*/
|
||||||
|
MedianCellSorter.prototype.compare = function(a, b)
|
||||||
|
{
|
||||||
|
if (a != null && b != null)
|
||||||
|
{
|
||||||
|
if (b.medianValue > a.medianValue)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (b.medianValue < a.medianValue)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user