2021-07-16 16:41:58 +02:00
|
|
|
/*
|
|
|
|
* Copyright [2015] [wisemapping]
|
|
|
|
*
|
|
|
|
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
|
|
|
|
* It is basically the Apache License, Version 2.0 (the "License") plus the
|
|
|
|
* "powered by wisemapping" text requirement on every single page;
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the license at
|
|
|
|
*
|
|
|
|
* http://www.wisemapping.org/license
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
const AbstractBasicSorter = require('./AbstractBasicSorter').default;
|
|
|
|
|
2021-09-02 18:32:23 +02:00
|
|
|
const SymmetricSorter = new Class(
|
|
|
|
/** @lends SymmetricSorter */ {
|
|
|
|
Extends: AbstractBasicSorter,
|
|
|
|
/**
|
|
|
|
* @constructs
|
|
|
|
* @extends mindplot.layout.AbstractBasicSorter
|
|
|
|
*/
|
|
|
|
initialize: function () {},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Predict the order and position of a dragged node.
|
|
|
|
*
|
|
|
|
* @param graph The tree set
|
|
|
|
* @param parent The parent of the node
|
|
|
|
* @param node The node
|
|
|
|
* @param position The position of the drag
|
|
|
|
* @param free Free drag or not
|
|
|
|
* @return {*}
|
|
|
|
*/
|
|
|
|
predict: function (graph, parent, node, position, free) {
|
|
|
|
var self = this;
|
|
|
|
var rootNode = graph.getRootNode(parent);
|
|
|
|
|
|
|
|
// If its a free node...
|
|
|
|
if (free) {
|
|
|
|
$assert(
|
|
|
|
$defined(position),
|
|
|
|
'position cannot be null for predict in free positioning'
|
|
|
|
);
|
|
|
|
$assert($defined(node), 'node cannot be null for predict in free positioning');
|
|
|
|
|
|
|
|
var direction = this._getRelativeDirection(
|
|
|
|
rootNode.getPosition(),
|
|
|
|
parent.getPosition()
|
|
|
|
);
|
|
|
|
var limitXPos =
|
|
|
|
parent.getPosition().x +
|
|
|
|
direction *
|
|
|
|
(parent.getSize().width / 2 +
|
|
|
|
node.getSize().width / 2 +
|
|
|
|
SymmetricSorter.INTERNODE_HORIZONTAL_PADDING);
|
|
|
|
|
|
|
|
var xPos =
|
|
|
|
direction > 0
|
|
|
|
? position.x >= limitXPos
|
|
|
|
? position.x
|
|
|
|
: limitXPos
|
|
|
|
: position.x <= limitXPos
|
|
|
|
? position.x
|
|
|
|
: limitXPos;
|
|
|
|
|
|
|
|
return [0, { x: xPos, y: position.y }];
|
|
|
|
}
|
2021-07-16 16:41:58 +02:00
|
|
|
|
2021-09-02 18:32:23 +02:00
|
|
|
// Its not a dragged node (it is being added)
|
|
|
|
if (!node) {
|
|
|
|
var parentDirection = self._getRelativeDirection(
|
|
|
|
rootNode.getPosition(),
|
|
|
|
parent.getPosition()
|
|
|
|
);
|
|
|
|
|
|
|
|
var position = {
|
|
|
|
x:
|
|
|
|
parent.getPosition().x +
|
|
|
|
parentDirection *
|
|
|
|
(parent.getSize().width + SymmetricSorter.INTERNODE_HORIZONTAL_PADDING),
|
|
|
|
y: parent.getPosition().y,
|
|
|
|
};
|
|
|
|
return [graph.getChildren(parent).length, position];
|
|
|
|
}
|
2021-07-16 16:41:58 +02:00
|
|
|
|
2021-09-02 18:32:23 +02:00
|
|
|
// If it is a dragged node...
|
|
|
|
$assert($defined(position), 'position cannot be null for predict in dragging');
|
|
|
|
var nodeDirection = this._getRelativeDirection(
|
|
|
|
rootNode.getPosition(),
|
|
|
|
node.getPosition()
|
|
|
|
);
|
|
|
|
var positionDirection = this._getRelativeDirection(rootNode.getPosition(), position);
|
|
|
|
var siblings = graph.getSiblings(node);
|
|
|
|
|
|
|
|
// node has no siblings and its trying to reconnect to its own parent
|
|
|
|
var sameParent = parent == graph.getParent(node);
|
|
|
|
if (siblings.length == 0 && nodeDirection == positionDirection && sameParent) {
|
|
|
|
return [node.getOrder(), node.getPosition()];
|
|
|
|
}
|
2021-07-16 16:41:58 +02:00
|
|
|
|
2021-09-02 18:32:23 +02:00
|
|
|
var parentChildren = graph.getChildren(parent);
|
|
|
|
|
|
|
|
if (parentChildren.length == 0) {
|
|
|
|
// Fit as a child of the parent node...
|
|
|
|
var position = {
|
|
|
|
x:
|
|
|
|
parent.getPosition().x +
|
|
|
|
positionDirection *
|
|
|
|
(parent.getSize().width + SymmetricSorter.INTERNODE_HORIZONTAL_PADDING),
|
|
|
|
y: parent.getPosition().y,
|
|
|
|
};
|
|
|
|
return [0, position];
|
|
|
|
} else {
|
|
|
|
// Try to fit within ...
|
|
|
|
var result = null;
|
|
|
|
var last = parentChildren.getLast();
|
|
|
|
for (var i = 0; i < parentChildren.length; i++) {
|
|
|
|
var parentChild = parentChildren[i];
|
|
|
|
var nodeAfter = i + 1 == parentChild.length ? null : parentChildren[i + 1];
|
|
|
|
|
|
|
|
// Fit at the bottom
|
|
|
|
if (!nodeAfter && position.y > parentChild.getPosition().y) {
|
|
|
|
var order =
|
|
|
|
graph.getParent(node) && graph.getParent(node).getId() == parent.getId()
|
|
|
|
? last.getOrder()
|
|
|
|
: last.getOrder() + 1;
|
|
|
|
var position = {
|
|
|
|
x: parentChild.getPosition().x,
|
|
|
|
y:
|
|
|
|
parentChild.getPosition().y +
|
|
|
|
parentChild.getSize().height +
|
|
|
|
SymmetricSorter.INTERNODE_VERTICAL_PADDING * 2,
|
|
|
|
};
|
|
|
|
return [order, position];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fit after this node
|
|
|
|
if (
|
|
|
|
nodeAfter &&
|
|
|
|
position.y > parentChild.getPosition().y &&
|
|
|
|
position.y < nodeAfter.getPosition().y
|
|
|
|
) {
|
|
|
|
if (
|
|
|
|
nodeAfter.getId() == node.getId() ||
|
|
|
|
parentChild.getId() == node.getId()
|
|
|
|
) {
|
|
|
|
return [node.getOrder(), node.getPosition()];
|
|
|
|
} else {
|
|
|
|
var order =
|
|
|
|
position.y > node.getPosition().y
|
|
|
|
? nodeAfter.getOrder() - 1
|
|
|
|
: parentChild.getOrder() + 1;
|
|
|
|
var position = {
|
|
|
|
x: parentChild.getPosition().x,
|
|
|
|
y:
|
|
|
|
parentChild.getPosition().y +
|
|
|
|
(nodeAfter.getPosition().y - parentChild.getPosition().y) / 2,
|
|
|
|
};
|
|
|
|
return [order, position];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-16 16:41:58 +02:00
|
|
|
|
2021-09-02 18:32:23 +02:00
|
|
|
// Position wasn't below any node, so it must be fitted above the first
|
|
|
|
var first = parentChildren[0];
|
2021-07-16 16:41:58 +02:00
|
|
|
var position = {
|
2021-09-02 18:32:23 +02:00
|
|
|
x: first.getPosition().x,
|
|
|
|
y:
|
|
|
|
first.getPosition().y -
|
|
|
|
first.getSize().height -
|
|
|
|
SymmetricSorter.INTERNODE_VERTICAL_PADDING * 2,
|
2021-07-16 16:41:58 +02:00
|
|
|
};
|
|
|
|
return [0, position];
|
2021-09-02 18:32:23 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param treeSet
|
|
|
|
* @param parent
|
|
|
|
* @param child
|
|
|
|
* @param order
|
|
|
|
* @throws will throw an error if the order is not strictly continuous
|
|
|
|
*/
|
|
|
|
insert: function (treeSet, parent, child, order) {
|
|
|
|
var children = this._getSortedChildren(treeSet, parent);
|
|
|
|
$assert(
|
|
|
|
order <= children.length,
|
|
|
|
'Order must be continues and can not have holes. Order:' + order
|
|
|
|
);
|
|
|
|
|
|
|
|
// Shift all the elements in one .
|
|
|
|
for (var i = order; i < children.length; i++) {
|
|
|
|
var node = children[i];
|
|
|
|
node.setOrder(i + 1);
|
|
|
|
}
|
|
|
|
child.setOrder(order);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param treeSet
|
|
|
|
* @param node
|
|
|
|
* @throws will throw an error if the node is in the wrong position*/
|
|
|
|
detach: function (treeSet, node) {
|
|
|
|
var parent = treeSet.getParent(node);
|
|
|
|
var children = this._getSortedChildren(treeSet, parent);
|
|
|
|
var order = node.getOrder();
|
|
|
|
$assert(children[order] === node, 'Node seems not to be in the right position');
|
|
|
|
|
|
|
|
// Shift all the nodes ...
|
|
|
|
for (var i = node.getOrder() + 1; i < children.length; i++) {
|
|
|
|
var child = children[i];
|
|
|
|
child.setOrder(child.getOrder() - 1);
|
|
|
|
}
|
|
|
|
node.setOrder(0);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param treeSet
|
|
|
|
* @param node
|
|
|
|
* @throws will throw an error if treeSet is null or undefined
|
|
|
|
* @throws will throw an error if node is null or undefined
|
|
|
|
* @throws will throw an error if the calculated x offset cannot be converted to a numeric
|
|
|
|
* value, is null or undefined
|
|
|
|
* @throws will throw an error if the calculated y offset cannot be converted to a numeric
|
|
|
|
* value, is null or undefined
|
|
|
|
* @return offsets
|
|
|
|
*/
|
|
|
|
computeOffsets: function (treeSet, node) {
|
|
|
|
$assert(treeSet, 'treeSet can no be null.');
|
|
|
|
$assert(node, 'node can no be null.');
|
|
|
|
|
|
|
|
var children = this._getSortedChildren(treeSet, node);
|
|
|
|
|
|
|
|
// Compute heights ...
|
|
|
|
var heights = children
|
|
|
|
.map(function (child) {
|
|
|
|
return {
|
|
|
|
id: child.getId(),
|
|
|
|
order: child.getOrder(),
|
|
|
|
position: child.getPosition(),
|
|
|
|
width: child.getSize().width,
|
|
|
|
height: this._computeChildrenHeight(treeSet, child),
|
2021-07-16 16:41:58 +02:00
|
|
|
};
|
2021-09-02 18:32:23 +02:00
|
|
|
}, this)
|
|
|
|
.reverse();
|
|
|
|
|
|
|
|
// Compute the center of the branch ...
|
|
|
|
var totalHeight = 0;
|
|
|
|
_.each(heights, function (elem) {
|
|
|
|
totalHeight += elem.height;
|
|
|
|
});
|
|
|
|
var ysum = totalHeight / 2;
|
|
|
|
|
|
|
|
// Calculate the offsets ...
|
|
|
|
var result = {};
|
|
|
|
for (var i = 0; i < heights.length; i++) {
|
|
|
|
ysum = ysum - heights[i].height;
|
|
|
|
var childNode = treeSet.find(heights[i].id);
|
|
|
|
var direction = this.getChildDirection(treeSet, childNode);
|
|
|
|
|
|
|
|
var yOffset = ysum + heights[i].height / 2;
|
|
|
|
var xOffset =
|
|
|
|
direction *
|
|
|
|
(heights[i].width / 2 +
|
|
|
|
node.getSize().width / 2 +
|
|
|
|
SymmetricSorter.INTERNODE_HORIZONTAL_PADDING);
|
|
|
|
|
|
|
|
$assert(!isNaN(xOffset), 'xOffset can not be null');
|
|
|
|
$assert(!isNaN(yOffset), 'yOffset can not be null');
|
|
|
|
|
|
|
|
result[heights[i].id] = { x: xOffset, y: yOffset };
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param treeSet
|
|
|
|
* @param node
|
|
|
|
* @throws will throw an error if order elements are missing
|
|
|
|
*/
|
|
|
|
verify: function (treeSet, node) {
|
|
|
|
// Check that all is consistent ...
|
|
|
|
var children = this._getSortedChildren(treeSet, node);
|
|
|
|
|
|
|
|
for (var i = 0; i < children.length; i++) {
|
|
|
|
$assert(children[i].getOrder() == i, 'missing order elements');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param treeSet
|
|
|
|
* @param child
|
|
|
|
* @return direction of the given child from its parent or from the root node, if isolated*/
|
|
|
|
getChildDirection: function (treeSet, child) {
|
|
|
|
$assert(treeSet, 'treeSet can no be null.');
|
|
|
|
$assert(treeSet.getParent(child), 'This should not happen');
|
|
|
|
|
|
|
|
var result;
|
|
|
|
var rootNode = treeSet.getRootNode(child);
|
|
|
|
if (treeSet.getParent(child) == rootNode) {
|
|
|
|
// This is the case of a isolated child ... In this case, the directions is based on the root.
|
|
|
|
result = Math.sign(rootNode.getPosition().x);
|
|
|
|
} else {
|
|
|
|
// if this is not the case, honor the direction of the parent ...
|
|
|
|
var parent = treeSet.getParent(child);
|
|
|
|
var grandParent = treeSet.getParent(parent);
|
|
|
|
var sorter = grandParent.getSorter();
|
|
|
|
result = sorter.getChildDirection(treeSet, parent);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
},
|
2021-07-16 16:41:58 +02:00
|
|
|
|
2021-09-02 18:32:23 +02:00
|
|
|
/** @return {String} the print name of this class */
|
|
|
|
toString: function () {
|
|
|
|
return 'Symmetric Sorter';
|
|
|
|
},
|
2021-07-16 16:41:58 +02:00
|
|
|
|
2021-09-02 18:32:23 +02:00
|
|
|
_getVerticalPadding: function () {
|
|
|
|
return SymmetricSorter.INTERNODE_VERTICAL_PADDING;
|
|
|
|
},
|
2021-07-16 16:41:58 +02:00
|
|
|
}
|
2021-09-02 18:32:23 +02:00
|
|
|
);
|
2021-07-16 16:41:58 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @constant
|
|
|
|
* @type {Number}
|
|
|
|
* @default
|
|
|
|
*/
|
|
|
|
SymmetricSorter.INTERNODE_VERTICAL_PADDING = 5;
|
|
|
|
/**
|
|
|
|
* @constant
|
|
|
|
* @type {Number}
|
|
|
|
* @default
|
|
|
|
*/
|
|
|
|
SymmetricSorter.INTERNODE_HORIZONTAL_PADDING = 30;
|
|
|
|
|
2021-09-02 18:32:23 +02:00
|
|
|
export default SymmetricSorter;
|