/*******************************************************************************
* Copyright (c) 2012-present Jakub Kováč, Jozef Brandýs, Katarína Kotrlová,
* Pavol Lukča, Ladislav Pápay, Viktor Tomkovič, Tatiana Tóthová
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package algvis.core;
import java.awt.Color;
import java.util.Hashtable;
import java.util.Stack;
import java.util.Vector;
import algvis.core.history.HashtableStoreSupport;
import algvis.core.visual.ZDepth;
import algvis.ui.view.View;
public class TreeNode extends Node {
private TreeNode child = null, right = null, parent = null;
// variables for the Reingold-Tilford-Walker layout
private int offset = 0; // offset from base line, base line has x-coord
// equaled to x-coord of leftmost child
private int level; // distance from the root
protected boolean thread = false; // is this node threaded?
private int toExtremeSon = 0; // offset from the leftmost son
private int toBaseline = 0; // distance to child's baseline
private int tmpx = 0;
private int tmpy = 0; // temporary coordinates of the node
private int number = 1;
private int change = 0;
private int shift = 0; // for evenly spaced smaller subtrees
// TreeNode ancestor = this; // unused variable for now
// statistics
private int size = 1;
private int height = 1;
public int nos = 0; // number of sons, probably useless
// from binary node
public int leftw, rightw;
protected TreeNode(DataStructure D, int key, int x, int y) {
super(D, key, x, y);
}
protected TreeNode(DataStructure D, int key, int zDepth) {
super(D, key, zDepth);
}
public boolean isRoot() {
return getParent() == null;
}
protected boolean isLeaf() {
return getChild() == null;
}
// calc() and calcTree() methods analogous to BSTNode ones
/**
* Calculate height and size of "this" node assuming these were calculated
* (properly) in its children.
*/
void calc() {
size = 1;
height = 1;
if (!isLeaf()) {
TreeNode w = getChild();
while (w != null) {
size += w.size;
if (height <= w.height) {
height = w.height + 1;
}
w = w.getRight();
}
}
}
/**
* Calculate height and size of subtree rooted by "this" node bottom-up
*/
void calcTree() {
if (!isLeaf()) {
TreeNode w = getChild();
while (w != null) {
w.calcTree();
w = w.getRight();
}
}
calc();
}
public void setArc() {
setArc(getParent());
}
public void drawEdges(View v) {
if (state != INVISIBLE) {
if (thread) {
v.setColor(Color.red);
if (getChild() != null) {
v.drawLine(x, y, getChild().x, getChild().y);
}
v.setColor(Color.black);
} else {
TreeNode w = getChild();
while (w != null) {
v.setColor(Color.black);
v.drawLine(x, y, w.x, w.y);
w.drawEdges(v);
w = w.getRight();
}
}
}
}
public void drawVertices(View v) {
TreeNode w = getChild();
while (w != null) {
w.drawVertices(v);
w = w.getRight();
}
draw(v);
}
/**
* Draw edges, then the node itself. Don't draw invisible nodes and edges
* from and to them
*/
protected void drawTree(View v) {
drawEdges(v);
drawVertices(v);
}
public void moveTree() {
TreeNode w = getChild();
while (w != null) {
w.moveTree();
w = w.getRight();
}
move();
}
/**
* Find the node at coordinates (x,y). This is used to identify the node
* that has been clicked by user.
*/
public TreeNode find(int x, int y) {
if (inside(x, y)) {
return this;
}
TreeNode w = getChild();
TreeNode res = null;
while ((w != null) && (res == null)) {
res = w.find(x, y);
w = w.getRight();
}
return res;
}
/**
* Rebox the whole subtree calculating the widths recursively bottom-up.
*/
public void reboxTree() {
final int bw = DataStructure.minsepx / 2;
int le = 9999999; // keeps current extreme leftw value
int re = -9999999;
TreeNode T = getChild();
while (T != null) {
T.reboxTree();
final int lxe = (T.tox - tox) - T.leftw;
if (lxe < le) {
le = lxe;
}
final int rxe = (T.tox - tox) + T.rightw;
if (rxe > re) {
re = rxe;
}
T = T.getRight();
}
if (le > -bw) {
le = -bw;
}
if (re < bw) {
re = bw;
}
leftw = -le;
rightw = re;
}
void addRight(TreeNode w) {
if (getRight() == null) {
setRight(w);
w.setParent(parent);
} else {
getRight().addRight(w);
}
}
public void addChild(TreeNode w) {
if (getChild() == null) {
setChild(w);
w.setParent(this);
w.D = this.D;
} else {
getChild().addRight(w);
}
}
public void deleteChild(TreeNode w) {
if (w == getChild()) {
setChild(getChild().getRight());
w.setRight(null);
} else {
TreeNode v = getChild();
while ((v != null) && (v.getRight() != w)) {
v = v.getRight();
}
if (v != null) {
v.setRight(w.getRight());
}
w.setRight(null);
}
}
public TreeNode leftmostChild() {
return getChild();
}
public TreeNode rightmostChild() {
TreeNode w = getChild();
if (w != null) {
while (w.getRight() != null) {
w = w.getRight();
}
}
return w;
}
void append(int x, int j) {
if (getKey() == x) {
addChild(new TreeNode(D, j, ZDepth.NODE));
} else {
TreeNode w = getChild();
while (w != null) {
w.append(x, j);
w = w.getRight();
}
}
}
public void reposition() {
fTRInitialization(0);
fTRPrePosition();
fTRDisposeThreads();
fTRPetrification(0);
fTRBounding(-tmpx);
reboxTree();
/*
* D.x1 -= D.minsepx; D.x2 += D.xspan + D.radius; D.y1 -= D.yspan +
* D.radius; D.y2 += D.yspan + D.radius;
*/
}
/**
* First traverse of tree in repositioning process. Set some basic
* variables.
*
* @param level
* current level in tree
*/
private void fTRInitialization(int level) {
this.level = level;
offset = shift = change = 0;
toExtremeSon = 0;
toBaseline = 0;
leftw = rightw = 0;
TreeNode w = getChild();
level++;
int noofchild = 1;
while (w != null) {
w.fTRInitialization(level);
w.number = noofchild;
w = w.getRight();
noofchild++;
}
}
/**
* The core of algorithm. Computes relative coordinates, uses threads to
* keep subtrees/subforests bounded and easy-track for spacing.
*
* @return Leftmost and rightmost nodes on the last level of subtree
*/
private NodePair<TreeNode> fTRPrePosition() {
NodePair<TreeNode> result = new NodePair<TreeNode>();
if (isLeaf()) {
offset = 0;
result.left = this;
result.right = this;
return result;
}
TreeNode LeftSubtree = getChild();
TreeNode RightSubtree = getChild().getRight();
/*
* So lets get result from first child
*/
final NodePair<TreeNode> fromLeftSubtree = LeftSubtree.fTRPrePosition();
result = fromLeftSubtree;
/*
* Spacing right subtree with left subforest. It will pre-compute shifts
* for distributing smaller subtrees.
*/
while (RightSubtree != null) {
final NodePair<TreeNode> fromRightSubtree = RightSubtree
.fTRPrePosition();
TreeNode L = LeftSubtree;
TreeNode R = RightSubtree;
int loffset = LeftSubtree.offset;
int roffset = RightSubtree.offset = LeftSubtree.offset;
while ((L != null) && (R != null)) {
final int distance = (loffset + DataStructure.minsepx - roffset);
if (distance > 0) {
RightSubtree.offset += distance;
roffset += distance;
/*
* set spacing for smaller subtrees
*/
TreeNode Elevator = L;
while (Elevator.getParent() != LeftSubtree.getParent()) {
Elevator = Elevator.getParent();
}
final int theta = RightSubtree.number - Elevator.number;
RightSubtree.change -= distance / theta;
RightSubtree.shift += distance;
Elevator.change += distance / theta;
}
/*
* I think this (Elevator) part causes O(n^2) (un)efficiency
*/
if (!L.thread) {
loffset += L.toExtremeSon;
} else {
loffset = 0;
TreeNode Elevator = L.getChild();
int cursep = 0;
while (Elevator.getParent() != LeftSubtree.getParent()) {
cursep += Elevator.offset
- Elevator.getParent().leftmostChild().offset
- Elevator.getParent().toExtremeSon;
Elevator = Elevator.getParent();
}
loffset = Elevator.offset + cursep;
}
if (!R.thread) {
roffset -= R.toExtremeSon;
} else {
roffset = 0;
TreeNode Elevator = R.getChild();
int cursep = 0;
do {
cursep += Elevator.offset
- Elevator.getParent().leftmostChild().offset
- Elevator.getParent().toExtremeSon;
Elevator = Elevator.getParent();
} while (Elevator != RightSubtree);
roffset = RightSubtree.offset + cursep;
}
L = L.rightmostChild();
R = R.leftmostChild();
}
/*
* L & R pointers are set right for making thread easy
*/
// both left subforest and right subtree have the same height
if ((L == null) && (R == null)) {
fromLeftSubtree.right = fromRightSubtree.right;
} else if ((L == null) && (R != null)) {
// left subforest is more shallow
fromLeftSubtree.left.thread = true;
fromLeftSubtree.left.setChild(R);
fromLeftSubtree.left = fromRightSubtree.left;
fromLeftSubtree.right = fromRightSubtree.right;
} else if ((L != null) && (R == null)) {
// right subtree is more shallow
fromRightSubtree.right.thread = true;
fromRightSubtree.right.setChild(L);
}
LeftSubtree = LeftSubtree.getRight();
RightSubtree = RightSubtree.getRight();
}
/*
* spaces smaller subtrees: a traverse of children from right to left
*/
int distance = 0;
int change = 0;
TreeNode w = getChild();
final Stack<TreeNode> stack = new Stack<TreeNode>();
while (w != null) {
final NodePair<TreeNode> N = new NodePair<TreeNode>();
N.left = w;
stack.push(w);
w = w.getRight();
}
while (!stack.empty()) {
w = stack.pop();
w.offset += distance;
change += w.change;
distance += w.shift + change;
}
/*
* finally, set proper offset to self
*/
toExtremeSon = (rightmostChild().offset - leftmostChild().offset) / 2;
toBaseline = leftmostChild().offset + toExtremeSon;
offset += toExtremeSon;
return fromLeftSubtree;
}
/**
* Disposes threads. Useful as stand-alone only for testing.
*/
void fTRDisposeThreads() {
if (thread) {
thread = false;
setChild(null);
}
TreeNode w = getChild();
while (w != null) {
w.fTRDisposeThreads();
w = w.getRight();
}
}
/**
* Changes relative coordinates to absolute. Spacing smaller subtrees works
* with absolute coordinates.
*
* @param baseline
* x-coordinate of the baseline
*/
private void fTRPetrification(int baseline) {
tmpx = baseline + offset;
tmpy = level * (DataStructure.minsepy);
TreeNode w = getChild();
while (w != null) {
w.fTRPetrification(tmpx - toBaseline);
w = w.getRight();
}
}
/**
* Final step of layout algorithm. Changes temporary coordinates to real
* one. And computes boundary of a tree.
*
* @param correction
* Useful when you want make root rooted at [0,0]
*/
private void fTRBounding(int correction) {
goTo(tmpx + correction, tmpy);
if (tox < D.x1) {
D.x1 = tox;
}
if (tox > D.x2) {
D.x2 = tox;
}
if (toy < D.y1) {
// this case should be always false
D.y1 = toy;
}
if (toy > D.y2) {
D.y2 = toy;
}
TreeNode w = getChild();
while (w != null) {
w.fTRBounding(correction);
w = w.getRight();
}
}
/**
* Beware! This method doesn't change bounds of the data structure!
*
* @param xamount
* amount of shift in x-axis
* @param yamount
* amount of shift in y-axis
*/
public void shift(int xamount, int yamount) {
goTo(tox + xamount, toy + yamount);
TreeNode w = getChild();
while (w != null) {
w.shift(xamount, yamount);
w = w.getRight();
}
}
public TreeNode getChild() {
return child;
}
public void setChild(TreeNode child) {
this.child = child;
}
public TreeNode getRight() {
return right;
}
public void setRight(TreeNode right) {
this.right = right;
}
protected TreeNode getParent() {
return parent;
}
public void setParent(TreeNode parent) {
this.parent = parent;
}
// private void fTRGetInfo(int phase, int variable) {
// System.out
// .println("Node: " + key + " fakex: " + fakex + " mod: "
// + modifier + " change: " + change + " shift: " + shift
// + " offset: " + offset + " toE: " + toExtremeSon
// + " toB: " + toBaseline + " thread: " + thread + " ("
// + phase + ")");
// }
private Vector<TreeNode> _getLeaves(Vector<TreeNode> result) {
if (isLeaf()) {
result.add(this);
return result;
} else {
result = child._getLeaves(result);
if (right != null) {
result = right._getLeaves(result);
}
return result;
}
}
public Vector<TreeNode> getLeaves() {
final Vector<TreeNode> result = new Vector<TreeNode>();
if (isLeaf()) {
result.add(this);
return result;
} else {
return child._getLeaves(result);
}
}
@Override
public void storeState(Hashtable<Object, Object> state) {
super.storeState(state);
HashtableStoreSupport.store(state, hash + "child", child);
HashtableStoreSupport.store(state, hash + "right", right);
HashtableStoreSupport.store(state, hash + "parent", parent);
if (child != null) {
child.storeState(state);
}
if (right != null) {
right.storeState(state);
}
}
@Override
public void restoreState(Hashtable<?, ?> state) {
super.restoreState(state);
final Object child = state.get(hash + "child");
if (child != null) {
this.child = (TreeNode) HashtableStoreSupport.restore(child);
}
final Object right = state.get(hash + "right");
if (right != null) {
this.right = (TreeNode) HashtableStoreSupport.restore(right);
}
final Object parent = state.get(hash + "parent");
if (parent != null) {
this.parent = (TreeNode) HashtableStoreSupport.restore(parent);
}
if (this.child != null) {
this.child.restoreState(state);
}
if (this.right != null) {
this.right.restoreState(state);
}
}
}