123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593 |
- /**
- * Copyright (c) 2006-2015, JGraph Ltd
- * Copyright (c) 2006-2015, Gaudenz Alder
- */
- /**
- * Class: mxFastOrganicLayout
- *
- * Extends <mxGraphLayout> to implement a fast organic layout algorithm.
- * The vertices need to be connected for this layout to work, vertices
- * with no connections are ignored.
- *
- * Example:
- *
- * (code)
- * var layout = new mxFastOrganicLayout(graph);
- * layout.execute(graph.getDefaultParent());
- * (end)
- *
- * Constructor: mxCompactTreeLayout
- *
- * Constructs a new fast organic layout for the specified graph.
- */
- function mxFastOrganicLayout(graph)
- {
- mxGraphLayout.call(this, graph);
- };
- /**
- * Extends mxGraphLayout.
- */
- mxFastOrganicLayout.prototype = new mxGraphLayout();
- mxFastOrganicLayout.prototype.constructor = mxFastOrganicLayout;
- /**
- * Variable: useInputOrigin
- *
- * Specifies if the top left corner of the input cells should be the origin
- * of the layout result. Default is true.
- */
- mxFastOrganicLayout.prototype.useInputOrigin = true;
- /**
- * Variable: resetEdges
- *
- * Specifies if all edge points of traversed edges should be removed.
- * Default is true.
- */
- mxFastOrganicLayout.prototype.resetEdges = true;
- /**
- * Variable: disableEdgeStyle
- *
- * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
- * modified by the result. Default is true.
- */
- mxFastOrganicLayout.prototype.disableEdgeStyle = true;
- /**
- * Variable: forceConstant
- *
- * The force constant by which the attractive forces are divided and the
- * replusive forces are multiple by the square of. The value equates to the
- * average radius there is of free space around each node. Default is 50.
- */
- mxFastOrganicLayout.prototype.forceConstant = 50;
- /**
- * Variable: forceConstantSquared
- *
- * Cache of <forceConstant>^2 for performance.
- */
- mxFastOrganicLayout.prototype.forceConstantSquared = 0;
- /**
- * Variable: minDistanceLimit
- *
- * Minimal distance limit. Default is 2. Prevents of
- * dividing by zero.
- */
- mxFastOrganicLayout.prototype.minDistanceLimit = 2;
- /**
- * Variable: maxDistanceLimit
- *
- * Maximal distance limit. Default is 500. Prevents of
- * dividing by zero.
- */
- mxFastOrganicLayout.prototype.maxDistanceLimit = 500;
- /**
- * Variable: minDistanceLimitSquared
- *
- * Cached version of <minDistanceLimit> squared.
- */
- mxFastOrganicLayout.prototype.minDistanceLimitSquared = 4;
- /**
- * Variable: initialTemp
- *
- * Start value of temperature. Default is 200.
- */
- mxFastOrganicLayout.prototype.initialTemp = 200;
- /**
- * Variable: temperature
- *
- * Temperature to limit displacement at later stages of layout.
- */
- mxFastOrganicLayout.prototype.temperature = 0;
- /**
- * Variable: maxIterations
- *
- * Total number of iterations to run the layout though.
- */
- mxFastOrganicLayout.prototype.maxIterations = 0;
- /**
- * Variable: iteration
- *
- * Current iteration count.
- */
- mxFastOrganicLayout.prototype.iteration = 0;
- /**
- * Variable: vertexArray
- *
- * An array of all vertices to be laid out.
- */
- mxFastOrganicLayout.prototype.vertexArray;
- /**
- * Variable: dispX
- *
- * An array of locally stored X co-ordinate displacements for the vertices.
- */
- mxFastOrganicLayout.prototype.dispX;
- /**
- * Variable: dispY
- *
- * An array of locally stored Y co-ordinate displacements for the vertices.
- */
- mxFastOrganicLayout.prototype.dispY;
- /**
- * Variable: cellLocation
- *
- * An array of locally stored co-ordinate positions for the vertices.
- */
- mxFastOrganicLayout.prototype.cellLocation;
- /**
- * Variable: radius
- *
- * The approximate radius of each cell, nodes only.
- */
- mxFastOrganicLayout.prototype.radius;
- /**
- * Variable: radiusSquared
- *
- * The approximate radius squared of each cell, nodes only.
- */
- mxFastOrganicLayout.prototype.radiusSquared;
- /**
- * Variable: isMoveable
- *
- * Array of booleans representing the movable states of the vertices.
- */
- mxFastOrganicLayout.prototype.isMoveable;
- /**
- * Variable: neighbours
- *
- * Local copy of cell neighbours.
- */
- mxFastOrganicLayout.prototype.neighbours;
- /**
- * Variable: indices
- *
- * Hashtable from cells to local indices.
- */
- mxFastOrganicLayout.prototype.indices;
- /**
- * Variable: allowedToRun
- *
- * Boolean flag that specifies if the layout is allowed to run. If this is
- * set to false, then the layout exits in the following iteration.
- */
- mxFastOrganicLayout.prototype.allowedToRun = true;
- /**
- * Function: isVertexIgnored
- *
- * Returns a boolean indicating if the given <mxCell> should be ignored as a
- * vertex. This returns true if the cell has no connections.
- *
- * Parameters:
- *
- * vertex - <mxCell> whose ignored state should be returned.
- */
- mxFastOrganicLayout.prototype.isVertexIgnored = function(vertex)
- {
- return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
- this.graph.getConnections(vertex).length == 0;
- };
- /**
- * Function: execute
- *
- * Implements <mxGraphLayout.execute>. This operates on all children of the
- * given parent where <isVertexIgnored> returns false.
- */
- mxFastOrganicLayout.prototype.execute = function(parent)
- {
- var model = this.graph.getModel();
- this.vertexArray = [];
- var cells = this.graph.getChildVertices(parent);
-
- for (var i = 0; i < cells.length; i++)
- {
- if (!this.isVertexIgnored(cells[i]))
- {
- this.vertexArray.push(cells[i]);
- }
- }
-
- var initialBounds = (this.useInputOrigin) ?
- this.graph.getBoundingBoxFromGeometry(this.vertexArray) :
- null;
- var n = this.vertexArray.length;
- this.indices = [];
- this.dispX = [];
- this.dispY = [];
- this.cellLocation = [];
- this.isMoveable = [];
- this.neighbours = [];
- this.radius = [];
- this.radiusSquared = [];
- if (this.forceConstant < 0.001)
- {
- this.forceConstant = 0.001;
- }
- this.forceConstantSquared = this.forceConstant * this.forceConstant;
- // Create a map of vertices first. This is required for the array of
- // arrays called neighbours which holds, for each vertex, a list of
- // ints which represents the neighbours cells to that vertex as
- // the indices into vertexArray
- for (var i = 0; i < this.vertexArray.length; i++)
- {
- var vertex = this.vertexArray[i];
- this.cellLocation[i] = [];
-
- // Set up the mapping from array indices to cells
- var id = mxObjectIdentity.get(vertex);
- this.indices[id] = i;
- var bounds = this.getVertexBounds(vertex);
- // Set the X,Y value of the internal version of the cell to
- // the center point of the vertex for better positioning
- var width = bounds.width;
- var height = bounds.height;
-
- // Randomize (0, 0) locations
- var x = bounds.x;
- var y = bounds.y;
-
- this.cellLocation[i][0] = x + width / 2.0;
- this.cellLocation[i][1] = y + height / 2.0;
- this.radius[i] = Math.min(width, height);
- this.radiusSquared[i] = this.radius[i] * this.radius[i];
- }
- // Moves cell location back to top-left from center locations used in
- // algorithm, resetting the edge points is part of the transaction
- model.beginUpdate();
- try
- {
- for (var i = 0; i < n; i++)
- {
- this.dispX[i] = 0;
- this.dispY[i] = 0;
- this.isMoveable[i] = this.isVertexMovable(this.vertexArray[i]);
- // Get lists of neighbours to all vertices, translate the cells
- // obtained in indices into vertexArray and store as an array
- // against the orginial cell index
- var edges = this.graph.getConnections(this.vertexArray[i], parent);
- var cells = this.graph.getOpposites(edges, this.vertexArray[i]);
- this.neighbours[i] = [];
- for (var j = 0; j < cells.length; j++)
- {
- // Resets the points on the traversed edge
- if (this.resetEdges)
- {
- this.graph.resetEdge(edges[j]);
- }
- if (this.disableEdgeStyle)
- {
- this.setEdgeStyleEnabled(edges[j], false);
- }
- // Looks the cell up in the indices dictionary
- var id = mxObjectIdentity.get(cells[j]);
- var index = this.indices[id];
- // Check the connected cell in part of the vertex list to be
- // acted on by this layout
- if (index != null)
- {
- this.neighbours[i][j] = index;
- }
- // Else if index of the other cell doesn't correspond to
- // any cell listed to be acted upon in this layout. Set
- // the index to the value of this vertex (a dummy self-loop)
- // so the attraction force of the edge is not calculated
- else
- {
- this.neighbours[i][j] = i;
- }
- }
- }
- this.temperature = this.initialTemp;
- // If max number of iterations has not been set, guess it
- if (this.maxIterations == 0)
- {
- this.maxIterations = 20 * Math.sqrt(n);
- }
-
- // Main iteration loop
- for (this.iteration = 0; this.iteration < this.maxIterations; this.iteration++)
- {
- if (!this.allowedToRun)
- {
- return;
- }
-
- // Calculate repulsive forces on all vertices
- this.calcRepulsion();
- // Calculate attractive forces through edges
- this.calcAttraction();
- this.calcPositions();
- this.reduceTemperature();
- }
- var minx = null;
- var miny = null;
-
- for (var i = 0; i < this.vertexArray.length; i++)
- {
- var vertex = this.vertexArray[i];
-
- if (this.isVertexMovable(vertex))
- {
- var bounds = this.getVertexBounds(vertex);
-
- if (bounds != null)
- {
- this.cellLocation[i][0] -= bounds.width / 2.0;
- this.cellLocation[i][1] -= bounds.height / 2.0;
-
- var x = this.graph.snap(Math.round(this.cellLocation[i][0]));
- var y = this.graph.snap(Math.round(this.cellLocation[i][1]));
-
- this.setVertexLocation(vertex, x, y);
-
- if (minx == null)
- {
- minx = x;
- }
- else
- {
- minx = Math.min(minx, x);
- }
-
- if (miny == null)
- {
- miny = y;
- }
- else
- {
- miny = Math.min(miny, y);
- }
- }
- }
- }
-
- // Modifies the cloned geometries in-place. Not needed
- // to clone the geometries again as we're in the same
- // undoable change.
- var dx = -(minx || 0) + 1;
- var dy = -(miny || 0) + 1;
-
- if (initialBounds != null)
- {
- dx += initialBounds.x;
- dy += initialBounds.y;
- }
-
- this.graph.moveCells(this.vertexArray, dx, dy);
- }
- finally
- {
- model.endUpdate();
- }
- };
- /**
- * Function: calcPositions
- *
- * Takes the displacements calculated for each cell and applies them to the
- * local cache of cell positions. Limits the displacement to the current
- * temperature.
- */
- mxFastOrganicLayout.prototype.calcPositions = function()
- {
- for (var index = 0; index < this.vertexArray.length; index++)
- {
- if (this.isMoveable[index])
- {
- // Get the distance of displacement for this node for this
- // iteration
- var deltaLength = Math.sqrt(this.dispX[index] * this.dispX[index] +
- this.dispY[index] * this.dispY[index]);
- if (deltaLength < 0.001)
- {
- deltaLength = 0.001;
- }
- // Scale down by the current temperature if less than the
- // displacement distance
- var newXDisp = this.dispX[index] / deltaLength
- * Math.min(deltaLength, this.temperature);
- var newYDisp = this.dispY[index] / deltaLength
- * Math.min(deltaLength, this.temperature);
- // reset displacements
- this.dispX[index] = 0;
- this.dispY[index] = 0;
- // Update the cached cell locations
- this.cellLocation[index][0] += newXDisp;
- this.cellLocation[index][1] += newYDisp;
- }
- }
- };
- /**
- * Function: calcAttraction
- *
- * Calculates the attractive forces between all laid out nodes linked by
- * edges
- */
- mxFastOrganicLayout.prototype.calcAttraction = function()
- {
- // Check the neighbours of each vertex and calculate the attractive
- // force of the edge connecting them
- for (var i = 0; i < this.vertexArray.length; i++)
- {
- for (var k = 0; k < this.neighbours[i].length; k++)
- {
- // Get the index of the othe cell in the vertex array
- var j = this.neighbours[i][k];
-
- // Do not proceed self-loops
- if (i != j &&
- this.isMoveable[i] &&
- this.isMoveable[j])
- {
- var xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];
- var yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];
- // The distance between the nodes
- var deltaLengthSquared = xDelta * xDelta + yDelta
- * yDelta - this.radiusSquared[i] - this.radiusSquared[j];
- if (deltaLengthSquared < this.minDistanceLimitSquared)
- {
- deltaLengthSquared = this.minDistanceLimitSquared;
- }
-
- var deltaLength = Math.sqrt(deltaLengthSquared);
- var force = (deltaLengthSquared) / this.forceConstant;
- var displacementX = (xDelta / deltaLength) * force;
- var displacementY = (yDelta / deltaLength) * force;
-
- this.dispX[i] -= displacementX;
- this.dispY[i] -= displacementY;
-
- this.dispX[j] += displacementX;
- this.dispY[j] += displacementY;
- }
- }
- }
- };
- /**
- * Function: calcRepulsion
- *
- * Calculates the repulsive forces between all laid out nodes
- */
- mxFastOrganicLayout.prototype.calcRepulsion = function()
- {
- var vertexCount = this.vertexArray.length;
- for (var i = 0; i < vertexCount; i++)
- {
- for (var j = i; j < vertexCount; j++)
- {
- // Exits if the layout is no longer allowed to run
- if (!this.allowedToRun)
- {
- return;
- }
- if (j != i &&
- this.isMoveable[i] &&
- this.isMoveable[j])
- {
- var xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];
- var yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];
- if (xDelta == 0)
- {
- xDelta = 0.01 + Math.random();
- }
-
- if (yDelta == 0)
- {
- yDelta = 0.01 + Math.random();
- }
-
- // Distance between nodes
- var deltaLength = Math.sqrt((xDelta * xDelta)
- + (yDelta * yDelta));
- var deltaLengthWithRadius = deltaLength - this.radius[i]
- - this.radius[j];
- if (deltaLengthWithRadius > this.maxDistanceLimit)
- {
- // Ignore vertices too far apart
- continue;
- }
- if (deltaLengthWithRadius < this.minDistanceLimit)
- {
- deltaLengthWithRadius = this.minDistanceLimit;
- }
- var force = this.forceConstantSquared / deltaLengthWithRadius;
- var displacementX = (xDelta / deltaLength) * force;
- var displacementY = (yDelta / deltaLength) * force;
-
- this.dispX[i] += displacementX;
- this.dispY[i] += displacementY;
- this.dispX[j] -= displacementX;
- this.dispY[j] -= displacementY;
- }
- }
- }
- };
- /**
- * Function: reduceTemperature
- *
- * Reduces the temperature of the layout from an initial setting in a linear
- * fashion to zero.
- */
- mxFastOrganicLayout.prototype.reduceTemperature = function()
- {
- this.temperature = this.initialTemp * (1.0 - this.iteration / this.maxIterations);
- };
- __mxOutput.mxFastOrganicLayout = typeof mxFastOrganicLayout !== 'undefined' ? mxFastOrganicLayout : undefined;
|