diff --git a/empress/support_files/js/bp-tree.js b/empress/support_files/js/bp-tree.js
index 8c737eedb..a9077da94 100644
--- a/empress/support_files/js/bp-tree.js
+++ b/empress/support_files/js/bp-tree.js
@@ -643,7 +643,7 @@ define(["ByteArray", "underscore"], function (ByteArray, _) {
      */
     BPTree.prototype.inOrderNodes = function () {
         if (this._inorder !== null) {
-            return this._inorder;
+            return _.clone(this._inorder);
         }
 
         // the root node of the tree
@@ -658,7 +658,7 @@ define(["ByteArray", "underscore"], function (ByteArray, _) {
             // append children to stack
             nodeStack = nodeStack.concat(this.getChildren(curNode));
         }
-        return this._inorder;
+        return _.clone(this._inorder);
     };
 
     /**
@@ -958,7 +958,7 @@ define(["ByteArray", "underscore"], function (ByteArray, _) {
      */
     BPTree.prototype.getNodesWithName = function (name) {
         if (name in this._nameToNodes) {
-            return this._nameToNodes[name];
+            return _.clone(this._nameToNodes[name]);
         }
 
         this._nameToNodes[name] = [];
@@ -968,7 +968,7 @@ define(["ByteArray", "underscore"], function (ByteArray, _) {
             }
         }
 
-        return this._nameToNodes[name];
+        return _.clone(this._nameToNodes[name]);
     };
 
     /**
@@ -980,7 +980,9 @@ define(["ByteArray", "underscore"], function (ByteArray, _) {
      *
      * @param {Set} keepTips The set of tip names to keep.
      *
-     * @return {BPTree} The new BPTree.
+     * @return {Object} An object containing the new tree ("tree") and two maps that
+     *                  convert the original postorder positions to the sheared
+     *                  tree postorder positions ("newToOld") and vice-versa ("oldToNew").
      */
     BPTree.prototype.shear = function (keepTips) {
         // closure
@@ -1022,6 +1024,9 @@ define(["ByteArray", "underscore"], function (ByteArray, _) {
         }
 
         var newBitArray = [];
+        var shearedToFull = new Map();
+        var fullToSheared = new Map();
+        var postorderPos = 1;
         for (i = 0; i < mask.length; i++) {
             if (mask[i] !== undefined) {
                 newBitArray.push(mask[i]);
@@ -1029,12 +1034,20 @@ define(["ByteArray", "underscore"], function (ByteArray, _) {
 
             // get name and length of node
             // Note: names and lengths of nodes are stored in postorder
+
             if (mask[i] === 0) {
                 names.push(this.name(i));
                 lengths.push(this.length(i));
+                shearedToFull.set(postorderPos, this.postorder(i));
+                fullToSheared.set(this.postorder(i), postorderPos);
+                postorderPos += 1;
             }
         }
-        return new BPTree(newBitArray, names, lengths, null);
+        return {
+            shearedToFull: shearedToFull,
+            fullToSheared: fullToSheared,
+            tree: new BPTree(newBitArray, names, lengths, null),
+        };
     };
 
     return BPTree;
diff --git a/empress/support_files/js/empress.js b/empress/support_files/js/empress.js
index 976c3faa4..a0424d231 100644
--- a/empress/support_files/js/empress.js
+++ b/empress/support_files/js/empress.js
@@ -11,6 +11,7 @@ define([
     "chroma",
     "LayoutsUtil",
     "ExportUtil",
+    "TreeController",
 ], function (
     _,
     Camera,
@@ -23,7 +24,8 @@ define([
     util,
     chroma,
     LayoutsUtil,
-    ExportUtil
+    ExportUtil,
+    TreeController
 ) {
     /**
      * @class EmpressTree
@@ -86,7 +88,7 @@ define([
          * The phylogenetic balance parenthesis tree
          * @private
          */
-        this._tree = tree;
+        this._tree = new TreeController(tree);
 
         /**
          * Used to index into _treeData
@@ -375,7 +377,9 @@ define([
      * Also updates this._maxDisplacement.
      */
     Empress.prototype.getLayoutInfo = function () {
-        var data, i;
+        var data,
+            i,
+            j = 1;
         // set up length getter
         var branchMethod = this.branchMethod;
         var checkLengthsChange = LayoutsUtil.shouldCheckBranchLengthsChanged(
@@ -383,13 +387,12 @@ define([
         );
         var lengthGetter = LayoutsUtil.getLengthMethod(
             branchMethod,
-            this._tree
+            this._tree.getTree()
         );
-
         // Rectangular
         if (this._currentLayout === "Rectangular") {
             data = LayoutsUtil.rectangularLayout(
-                this._tree,
+                this._tree.getTree(),
                 4020,
                 4020,
                 // since lengths for "ignoreLengths" are set by `lengthGetter`,
@@ -404,21 +407,22 @@ define([
                 checkLengthsChange
             );
             this._yrscf = data.yScalingFactor;
-            for (i = 1; i <= this._tree.size; i++) {
+            for (i of this._tree.postorderTraversal((includeRoot = true))) {
                 // remove old layout information
                 this._treeData[i].length = this._numOfNonLayoutParams;
 
                 // store new layout information
-                this._treeData[i][this._tdToInd.xr] = data.xCoord[i];
-                this._treeData[i][this._tdToInd.yr] = data.yCoord[i];
+                this._treeData[i][this._tdToInd.xr] = data.xCoord[j];
+                this._treeData[i][this._tdToInd.yr] = data.yCoord[j];
                 this._treeData[i][this._tdToInd.highestchildyr] =
-                    data.highestChildYr[i];
+                    data.highestChildYr[j];
                 this._treeData[i][this._tdToInd.lowestchildyr] =
-                    data.lowestChildYr[i];
+                    data.lowestChildYr[j];
+                j += 1;
             }
         } else if (this._currentLayout === "Circular") {
             data = LayoutsUtil.circularLayout(
-                this._tree,
+                this._tree.getTree(),
                 4020,
                 4020,
                 this.leafSorting,
@@ -426,39 +430,41 @@ define([
                 lengthGetter,
                 checkLengthsChange
             );
-            for (i = 1; i <= this._tree.size; i++) {
+            for (i of this._tree.postorderTraversal((includeRoot = true))) {
                 // remove old layout information
                 this._treeData[i].length = this._numOfNonLayoutParams;
 
                 // store new layout information
-                this._treeData[i][this._tdToInd.xc0] = data.x0[i];
-                this._treeData[i][this._tdToInd.yc0] = data.y0[i];
-                this._treeData[i][this._tdToInd.xc1] = data.x1[i];
-                this._treeData[i][this._tdToInd.yc1] = data.y1[i];
-                this._treeData[i][this._tdToInd.angle] = data.angle[i];
-                this._treeData[i][this._tdToInd.arcx0] = data.arcx0[i];
-                this._treeData[i][this._tdToInd.arcy0] = data.arcy0[i];
+                this._treeData[i][this._tdToInd.xc0] = data.x0[j];
+                this._treeData[i][this._tdToInd.yc0] = data.y0[j];
+                this._treeData[i][this._tdToInd.xc1] = data.x1[j];
+                this._treeData[i][this._tdToInd.yc1] = data.y1[j];
+                this._treeData[i][this._tdToInd.angle] = data.angle[j];
+                this._treeData[i][this._tdToInd.arcx0] = data.arcx0[j];
+                this._treeData[i][this._tdToInd.arcy0] = data.arcy0[j];
                 this._treeData[i][this._tdToInd.arcstartangle] =
-                    data.arcStartAngle[i];
+                    data.arcStartAngle[j];
                 this._treeData[i][this._tdToInd.arcendangle] =
-                    data.arcEndAngle[i];
+                    data.arcEndAngle[j];
+                j += 1;
             }
         } else {
             data = LayoutsUtil.unrootedLayout(
-                this._tree,
+                this._tree.getTree(),
                 4020,
                 4020,
                 undefined,
                 lengthGetter,
                 checkLengthsChange
             );
-            for (i = 1; i <= this._tree.size; i++) {
+            for (i of this._tree.postorderTraversal((includeRoot = true))) {
                 // remove old layout information
                 this._treeData[i].length = this._numOfNonLayoutParams;
 
                 // store new layout information
-                this._treeData[i][this._tdToInd.x2] = data.xCoord[i];
-                this._treeData[i][this._tdToInd.y2] = data.yCoord[i];
+                this._treeData[i][this._tdToInd.x2] = data.xCoord[j];
+                this._treeData[i][this._tdToInd.y2] = data.yCoord[j];
+                j += 1;
             }
         }
         this._drawer.loadTreeCoordsBuff(this.getTreeCoords());
@@ -595,7 +601,7 @@ define([
             );
         }
         // iterate through the tree in postorder, skip root
-        for (var node = 1; node < tree.size; node++) {
+        for (var node of this._tree.postorderTraversal()) {
             // name of current node
             // var node = this._treeData[node];
             var parent = tree.postorder(
@@ -724,7 +730,7 @@ define([
             addPoint();
         }
         // iterate through the tree in postorder, skip root
-        for (var node = 1; node < tree.size; node++) {
+        for (var node of this._tree.postorderTraversal()) {
             if (!this.getNodeInfo(node, "visible")) {
                 continue;
             }
@@ -891,7 +897,7 @@ define([
             throw new Error("getNodeCoords() drawNodeCircles is out of range");
         }
 
-        for (var node = 1; node <= tree.size; node++) {
+        for (var node of this._tree.postorderTraversal((includeRoot = true))) {
             if (!comp(node)) {
                 continue;
             }
@@ -1232,7 +1238,7 @@ define([
             this._addThickVerticalLineCoords(coords, tree.size, lwScaled);
         }
         // iterate through the tree in postorder, skip root
-        for (var node = 1; node < this._tree.size; node++) {
+        for (var node of this._tree.postorderTraversal()) {
             // name of current node
             var parent = tree.postorder(
                 tree.parent(tree.postorderselect(node))
@@ -1448,7 +1454,7 @@ define([
             this._maxDisplacement = null;
             return;
         }
-        for (var node = 1; node < this._tree.size; node++) {
+        for (var node of this._tree.postorderTraversal()) {
             if (this._tree.isleaf(this._tree.postorderselect(node))) {
                 maxD = this[compFunc](node, maxD);
             }
@@ -1936,7 +1942,7 @@ define([
         } else {
             halfAngleRange = Math.PI / this._tree.numleaves();
         }
-        for (node = 1; node < this._tree.size; node++) {
+        for (var node of this._tree.postorderTraversal()) {
             if (this._tree.isleaf(this._tree.postorderselect(node))) {
                 var name = this.getNodeInfo(node, "name");
                 var fm;
@@ -2069,7 +2075,7 @@ define([
         // For the circular layout, how to speed this up is less clear -- I
         // suspect it should be possible using WebGL and some fancy
         // trigonometry somehow, but I'm not sure.
-        for (var node = 1; node < this._tree.size; node++) {
+        for (var node of this._tree.postorderTraversal()) {
             if (this._tree.isleaf(this._tree.postorderselect(node))) {
                 if (this._currentLayout === "Rectangular") {
                     var y = this.getY(node);
@@ -2376,7 +2382,7 @@ define([
         if (!ignoreAbsentTips) {
             // find "non-represented" tips
             // Note: the following uses postorder traversal
-            for (i = 1; i < tree.size; i++) {
+            for (i of this._tree.postorderTraversal()) {
                 if (tree.isleaf(tree.postorderselect(i))) {
                     var represented = false;
                     for (j = 0; j < categories.length; j++) {
@@ -2396,7 +2402,7 @@ define([
         // root (at index tree.size) in this loop, we iterate over all its
         // descendants; so in the event that all leaves are unique,
         // the root can still get assigned to a group.
-        for (i = 1; i < tree.size; i++) {
+        for (i of this._tree.postorderTraversal()) {
             var node = i;
             var parent = tree.postorder(tree.parent(tree.postorderselect(i)));
 
@@ -2676,7 +2682,7 @@ define([
         var x = 0,
             y = 0,
             zoomAmount = 0;
-        for (var node = 1; node <= this._tree.size; node++) {
+        for (var node of this._tree.postorderTraversal((includeRoot = true))) {
             // node = this._treeData[node];
             x += this.getX(node);
             y += this.getY(node);
@@ -2767,7 +2773,7 @@ define([
         this._collapsedClades = {};
         // Note: currently collapseClades is the only method that set
         // the node visibility property.
-        for (var i = 1; i <= this._tree.size; i++) {
+        for (var i of this._tree.postorderTraversal((includeRoot = true))) {
             this.setNodeInfo(i, "visible", true);
         }
 
@@ -2807,7 +2813,7 @@ define([
         // was not called. Thus, this loop is used to guarantee that if an
         // internal node belongs to a group then all of its descendants belong
         // to the same group.
-        for (var i = 1; i <= this._tree.size; i++) {
+        for (var i of this._tree.postorderTraversal()) {
             var parent = this._tree.postorder(
                 this._tree.parent(this._tree.postorderselect(i))
             );
@@ -2823,10 +2829,7 @@ define([
         // collaped.
         // Collapsing a clade will set the .visible property of members to
         // false and will then be skipped in the for loop.
-        var inorder = this._tree.inOrderNodes();
-        for (var node in inorder) {
-            node = inorder[node];
-
+        for (var node of this._tree.inOrderTraversal()) {
             // dont collapse clade
             if (this._dontCollapse.has(node)) {
                 continue;
diff --git a/empress/support_files/js/tree-controller.js b/empress/support_files/js/tree-controller.js
new file mode 100644
index 000000000..d4f8d6006
--- /dev/null
+++ b/empress/support_files/js/tree-controller.js
@@ -0,0 +1,475 @@
+define(["LayoutsUtil", "Colorer"], function (LayoutsUtil, Colorer) {
+    function TreeModel(tree) {
+        this.shearedTree = tree;
+        this.fullTree = tree;
+        this.shearedToFull = new Map();
+        this.fullToSheared = new Map();
+
+        // initialize
+        for (var i = 1; i <= this.shearedTree.size; i++) {
+            this.fullToSheared.set(i, i);
+            this.shearedToFull.set(i, i);
+        }
+    }
+
+    TreeModel.prototype.getTree = function () {
+        return this.shearedTree;
+    };
+
+    TreeModel.prototype.shear = function (tips) {
+        var result = this.fullTree.shear(tips);
+        this.shearedTree = result.tree;
+        this.shearedToFull = result.shearedToFull;
+        this.fullToSheared = result.fullToSheared;
+    };
+
+    TreeModel.prototype.unshear = function () {
+        this.shearedTree = this.fullTree;
+        for (var i = 1; i <= this.shearedTree.size; i++) {
+            this.fullToSheared.set(i, i);
+            this.shearedToFull.set(i, i);
+        }
+    };
+
+    TreeModel.prototype.postorderTraversal = function* (includeRoot = false) {
+        var nodes = [],
+            i;
+        for (i = 1; i <= this.shearedToFull.size; i++) {
+            nodes.push(this.shearedToFull.get(i));
+        }
+        if (!includeRoot) {
+            nodes.pop();
+        }
+
+        yield* nodes;
+    };
+
+    function TreeController(tree) {
+        /**
+         *
+         * @class TreeController
+         *
+         * Initialzes a new TreeController. This class is extends BPTree and allows
+         * EMPress to dynamically shear the tree. TreeController's UI is similar to
+         * BPTree. The input/output to all functions shared between TreeController
+         * and BPTree are in respect to the original tree. For example,
+         * postorderselect(5) will return the index of the 5th node in a postorder
+         * traversal of the original tree. However, TreeController implements a new
+         * function __curToOrigNodeFunction() that uses the topology of the sheared
+         * tree to execute fchild, lchild, nsibling, and psibling. Thus,
+         * fchild(5) will return the first child of node 5 in the sheared tree.
+         * However, the input/output of fchild, lchild, nsibling, and psibling are
+         * still in relation to the original tree. So, fchild(5) means the first
+         * child of a node in the sheared tree that corresponds to the 5th node
+         * found in a post order traversal of the original tree. In addition the
+         * traversal methods such as postorderTraversal will also use the topology
+         * of the sheared tree but will output the results in relation to the
+         * original tree. The reason for this behavior is due to the fact that
+         * empress uses a nodes postorder postion (in the orginal tree) as its key
+         * in the various metadata structures.
+         *
+         * @param {BPTree} tree This should be the original BPTree created when
+         *                      initializing empress.
+         *
+         * @return {TreeController}
+         * @constructs TreeController
+         */
+        this.model = new TreeModel(tree);
+        this.size = this.model.fullTree.size;
+    }
+
+    /**
+     * Returns the current (sheared) tree
+     *
+     * @return {BPTree}
+     */
+    TreeController.prototype.getTree = function () {
+        return this.model.getTree();
+    };
+
+    /**
+     * Removes nodes from the original tree until only the nodes found in tips
+     * and there ancestors remain in the tree.
+     *
+     * @param{Set} tips A set of tip names that will be kept.
+     */
+    TreeController.prototype.shear = function (tips) {
+        this.model.shear(tips);
+    };
+
+    /**
+     * Restores the original tree.
+     */
+    TreeController.prototype.unshear = function () {
+        this.model.unshear();
+    };
+
+    /**
+     * Returns an iterator for nodes in a post order traversal of the sheared
+     * tree.
+     *
+     * Note: This method will use the topology of the currect tree but will
+     *       return the nodes position in the original tree.
+     *
+     * @param{Boolean} includeRoot If true then the root will be included.
+     */
+    TreeController.prototype.postorderTraversal = function* (
+        includeRoot = false
+    ) {
+        yield* this.model.postorderTraversal(includeRoot);
+    };
+
+    /**
+     * Returns an Object describing the minimum, maximum, and average of all
+     * non-root node lengths in the sheared tree.
+     *
+     * @return {Object} Contains three keys: "min", "max", and "avg", mapping
+     *                  to Numbers representing the minimum, maximum, and
+     *                  average non-root node length in the tree.
+     */
+    TreeController.prototype.getLengthStats = function () {
+        return this.model.shearedTree.getLengthStats();
+    };
+
+    /**
+     * Return the name of the ith index in the ORIGINAL bp tree.
+     *
+     * Note: The input of this method should the result of either preorderselect
+     *       or postorderselect.
+     *
+     *
+     * @param{Number} i The index corresponding to a node in the ORIGINAL tree
+     *
+     * @return{String}
+     */
+    TreeController.prototype.name = function (i) {
+        return this.model.fullTree.name(i);
+    };
+
+    /**
+     * Returns an array of all node names in sheared tree.
+     */
+    TreeController.prototype.getAllNames = function () {
+        return this.model.shearedTree.getAllNames();
+    };
+
+    /**
+     * Returns the number of leaf nodes in sheared tree
+     *
+     * @return {Number}
+     */
+    TreeController.prototype.numleaves = function () {
+        return this.model.shearedTree.numleaves();
+    };
+
+    /**
+     * Returns the length of the ith index in the ORIGINAL bp tree.
+     *
+     * Note: The input of this method should the result of either preorderselect
+     *       or postorderselect.
+     *
+     * @param{Number} i The index corresponding to a node in the ORIGINAL tree
+     *
+     * @return{Number}
+     */
+    TreeController.prototype.length = function (i) {
+        return this.model.fullTree.length(i);
+    };
+
+    /**
+     * Return the parent index of the node that corresponds to the ith index in
+     * the ORIGINAL bp tree.
+     *
+     * Note: The input of this method should the result of either preorderselect
+     *       or postorderselect.
+     *
+     * Note: The output of this method is also in relation to the original tree.
+     *
+     * @param{Number} i The index corresponding to a node in the ORIGINAL tree
+     *
+     * @return{Number}
+     */
+    TreeController.prototype.parent = function (i) {
+        return this.model.fullTree.parent(i);
+    };
+
+    /**
+     * Returns the index of the opening index of the root node.
+     *
+     * Note: This will always be 0.
+     *
+     * @return {Number}
+     */
+    TreeController.prototype.root = function () {
+        return this.model.fullTree.root();
+    };
+
+    /**
+     * Returns true if i represents a leaf node
+     *
+     * Note: The input of this method should the result of either preorderselect
+     *       or postorderselect.
+     *
+     * @param{Number} i The index corresponding to a node in the ORIGINAL tree
+     *
+     * @return {Boolean}
+     */
+    TreeController.prototype.isleaf = function (i) {
+        return this.model.fullTree.isleaf(i);
+    };
+
+    /**
+     * This method is used in fchild, lchild, nsibling, and psibling and is what
+     * allows TreeController to use the topology of the sheared tree but returns
+     * the results w.r.t the original tree.
+     *
+     * @param{Number} i The index correspond to a node in the ORIGINAL tree.
+     * @param{String} func The function to use. This should only be fchild,
+     *                     nchild, nsibling or psibling.
+     *
+     * @return{Number} The result of func w.r.t the ORIGINAL tree.
+     */
+
+    TreeController.prototype._shearedToFullNodeFunction = function (i, func) {
+        var shearedTreeTree = this.model.shearedTree;
+        var fullTree = this.model.fullTree;
+
+        var node = shearedTreeTree.postorderselect(
+            this.model.fullToSheared.get(fullTree.postorder(i))
+        );
+
+        node = shearedTreeTree.postorder(shearedTreeTree[func](node));
+        node = fullTree.postorderselect(this.model.shearedToFull.get(node));
+        return node;
+    };
+
+    /**
+     * Returns the opening index of first child of the node represented by i.
+     * This method will use the topology of the sheared (sheared) tree but its
+     * input and output will be w.r.t the ORGINAL tree.
+     *
+     * Note: The input of this method should the result of either preorderselect
+     *       or postorderselect.
+     *
+     * @param{Number} i The index corresponding to a node in the ORIGINAL tree
+     *
+     * @return {Number} return 0 if i is a leaf node
+     */
+    TreeController.prototype.fchild = function (i) {
+        return this._shearedToFullNodeFunction(i, "fchild");
+    };
+
+    /**
+     * Returns the opening index of last child of the node represented by i.
+     * This method will use the topology of the sheared (sheared) tree but its
+     * input and output will be w.r.t the ORGINAL tree.
+     *
+     * Note: The input of this method should the result of either preorderselect
+     *       or postorderselect.
+     *
+     * @param{Number} i The index corresponding to a node in the ORIGINAL tree
+     *
+     * @return {Number} return 0 if i is a leaf node
+     */
+    TreeController.prototype.lchild = function (i) {
+        return this._shearedToFullNodeFunction(i, "lchild");
+    };
+
+    /**
+     * Returns the opening index of next sibling of the node represented by i.
+     * This method will use the topology of the sheared (sheared) tree but its
+     * input and output will be w.r.t the ORGINAL tree.
+     *
+     * Note: The input of this method should the result of either preorderselect
+     *       or postorderselect.
+     *
+     * @param{Number} i The index corresponding to a node in the ORIGINAL tree
+     *
+     * @return {Number} return 0 if i does not have a next sibling
+     */
+    TreeController.prototype.nsibling = function (i) {
+        return this._shearedToFullNodeFunction(i, "nsibling");
+    };
+
+    /**
+     * Returns the opening index of previous sibling of the node represented by
+     * i. This method will use the topology of the sheared (sheared) tree but
+     * its input and output will be w.r.t the ORGINAL tree.
+     *
+     * Note: The input of this method should the result of either preorderselect
+     *       or postorderselect.
+     *
+     * @param{Number} i The index corresponding to a node in the ORIGINAL tree
+     *
+     * @return {Number} return 0 if i does not have a previous sibling
+     */
+    TreeController.prototype.psibling = function (i) {
+        return this._shearedToFullNodeFunction(i, "psibling");
+    };
+
+    /**
+     * Returns the postorder rank of index i in the ORIGINAL tree.
+     *
+     * Note: The input of this method should the result of parent, fchild,
+     *       lchild, nsibling or psibling.
+     *
+     * @param {Number} i The index to assess postorder rank
+     *
+     * @return {Number} The postorder rank of index i
+     */
+    TreeController.prototype.postorder = function (i) {
+        return this.model.fullTree.postorder(i);
+    };
+
+    /**
+     * Find the index of the node with postorder k in the ORIGINAL tree.
+     *
+     * @param {Number} k The postorder to search for
+     *                 Note: k starts at 1
+     *
+     * @return {Number} The index position of the node in the tree
+     */
+    TreeController.prototype.postorderselect = function (k) {
+        return this.model.fullTree.postorderselect(k);
+    };
+
+    /**
+     * Returns the preorder rank of index i in the ORIGINAL tree.
+     *
+     * Note: The input of this method should the result of parent, fchild,
+     *       lchild, nsibling or psibling.
+     *
+     * @param {Number} i The index to assess preorder rank
+     *
+     * @return {Number} The preorder rank of index i
+     */
+    TreeController.prototype.preorder = function (i) {
+        return this.model.fullTree.preorder(i);
+    };
+
+    /**
+     * Find the index of the node with preorder k in the ORIGINAL tree.
+     *
+     * @param {Number} k The preorder to search for.
+     *                 Note: k starts at 1.
+     *
+     * @return {Number} The index position of the node in the tree
+     */
+    TreeController.prototype.preorderselect = function (k) {
+        return this.model.fullTree.preorderselect(k);
+    };
+
+    /**
+     * Returns an iterator for nodes in an in-order traversal of the sheared
+     * tree.
+     *
+     * Note: This method will use the topology of the currect tree but will
+     *       return the nodes position in the original tree.
+     *
+     * @param{Boolean} includeRoot If true then the root will be included.
+     */
+    TreeController.prototype.inOrderTraversal = function* (
+        includeRoot = false
+    ) {
+        var inOrderNodes = this.model.shearedTree.inOrderNodes();
+        for (var i = 0; i < inOrderNodes.length; i++) {
+            inOrderNodes[i] = this.model.shearedToFull.get(inOrderNodes[i]);
+        }
+        if (!includeRoot) {
+            inOrderNodes.shift();
+        }
+        yield* inOrderNodes;
+    };
+
+    /**
+     * Finds the sum of lengths from start to end. This method will use the
+     * topology of the sheared tree but its input must be w.r.t the ORIGINAL
+     * tree.
+     *
+     * Note: start must be a descendant of end. An error will be thrown if start
+     *       is not a descendant of end. Also, this method does not take into
+     *       account the length of end since that length would represent the
+     *       length of end to its parent.
+     *
+     * @param {Number} start The postorder position of a node
+     * @param {Number} end The postorder position of a node
+     * @param {Boolean} ignoreLengths If truthy, treat all node lengths as 1;
+     *                                if falsy, actually consider node lengths
+     *
+     * @return {Number} the sum of length from start to end
+     */
+    TreeController.prototype.getTotalLength = function (
+        start,
+        end,
+        ignoreLengths
+    ) {
+        start = this.model.fullToSheared.get(start);
+        end = this.model.fullToSheared.get(end);
+        return this.model.shearedTree.getTotalLength(start, end, ignoreLengths);
+    };
+
+    /**
+     * Retrieve the tips in the subtree of a given (internal) node key. This
+     * method will use the topology of the sheared tree but its input/output
+     * will be w.r.t the ORIGINAL tree.
+     *
+     * @param {Number} nodeKey The post-order position of a node in the ORIGINAL
+     *                 tree
+     *
+     * @return {Array} tips Tips of the subtree.
+     */
+    TreeController.prototype.findTips = function (nodeKey) {
+        nodeKey = this.model.fullToSheared.get(nodeKey);
+        var tips = this.model.shearedTree.findTips(nodeKey);
+        for (var i = 0; i < tips.length; i++) {
+            tips[i] = this.model.shearedToFull.get(tips[i]);
+        }
+        return tips;
+    };
+
+    /**
+     * Retrieve number of tips in the subtree of a given node. This method will
+     * use the topology of the sheared tree but its input must be w.r.t the
+     * ORIGINAL tree.
+     *
+     * @param {Integer} nodeKey The postorder position of a node in the ORIGINAL
+     *                  tree
+     *
+     * @return {Integer} The number of tips on the subtree rooted at nodeKey.
+     */
+    TreeController.prototype.getNumTips = function (nodeKey) {
+        nodeKey = this.model.fullToSheared.get(nodeKey);
+        return this.model.shearedTree.getNumTips(nodeKey);
+    };
+
+    /**
+     * Checks to see if name is in the sheared tree.
+     *
+     * @param {String} name The name to search for.
+     *
+     * @return {Boolean} If the name is in the tree.
+     */
+    TreeController.prototype.containsNode = function (name) {
+        return this.model.shearedTree.containsNode(name);
+    };
+
+    /**
+     * Returns all nodes with a given name. This method will use the topology
+     * of the sheared tree but its output will be w.r.t the ORIGINAL tree.
+     *
+     * @param {String} name The name of the node(s)
+     *
+     * @return {Array} An array of postorder positions of nodes with a given
+     *                 name. If no nodes have the specified name, this will be
+     *                 an empty array.
+     */
+    TreeController.prototype.getNodesWithName = function (name) {
+        var nodes = this.model.shearedTree.getNodesWithName(name);
+        for (var i = 0; i < nodes.length; i++) {
+            nodes[i] = this.model.shearedToFull.get(nodes[i]);
+        }
+        return nodes;
+    };
+
+    return TreeController;
+});
diff --git a/empress/support_files/templates/empress-template.html b/empress/support_files/templates/empress-template.html
index 9aafaa5b8..8a933ebd3 100644
--- a/empress/support_files/templates/empress-template.html
+++ b/empress/support_files/templates/empress-template.html
@@ -115,6 +115,7 @@ <h3 id="menu-sm-header">Sample Presence Information</h3>
             'util' : './js/util',
             'LayoutsUtil': './js/layouts-util',
             'ExportUtil': './js/export-util',
+            'TreeController': './js/tree-controller'
         }
     });
 
diff --git a/tests/index.html b/tests/index.html
index b39ad8f52..78f79dc83 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -203,6 +203,7 @@ <h3 id="menu-sm-header">Sample Presence Information</h3>
           'SelectedNodeMenu' : './support_files/js/select-node-menu',
           'LayoutsUtil' : './support_files/js/layouts-util',
           'ExportUtil' : './support_files/js/export-util',
+          'TreeController' : './support_files/js/tree-controller',
 
           /* test utility code */
           'UtilitiesForTesting' : './../tests/utilities-for-testing',
@@ -223,12 +224,14 @@ <h3 id="menu-sm-header">Sample Presence Information</h3>
           'testLegend': './../tests/test-legend',
           'testLayoutsUtil': './../tests/test-layouts-util',
           'testSelectedNodeMenu': './../tests/test-select-node-menu',
+          'testTreeController': './../tests/test-tree-controller',
         }
     });
 
     // load tests
     require(
-        ['jquery',
+        [
+        'jquery',
          'glMatrix',
          'chroma',
          'underscore',
@@ -262,6 +265,7 @@ <h3 id="menu-sm-header">Sample Presence Information</h3>
          'testLegend',
          'testLayoutsUtil',
          'testSelectedNodeMenu',
+         'testTreeController',
          ],
 
         // start tests
@@ -300,6 +304,7 @@ <h3 id="menu-sm-header">Sample Presence Information</h3>
           testLegend,
           testLayoutsUtil,
           testSelectedNodeMenu,
+          testTreeController
         ) {
             $(document).ready(function() {
                 QUnit.start();
diff --git a/tests/test-bp-tree.js b/tests/test-bp-tree.js
index 78bff215d..2d8c56eaf 100644
--- a/tests/test-bp-tree.js
+++ b/tests/test-bp-tree.js
@@ -929,8 +929,32 @@ require(["jquery", "ByteArray", "BPTree"], function ($, ByteArray, BPTree) {
             );
 
             var keep = new Set(["4", "6", "7", "10", "11"]);
+            var shearedToFull = new Map([
+                [1, 2],
+                [2, 3],
+                [3, 4],
+                [4, 5],
+                [5, 6],
+                [6, 7],
+                [7, 8],
+                [8, 9],
+                [9, 10],
+                [10, 11],
+            ]);
+            var fullToSheared = new Map([
+                [2, 1],
+                [3, 2],
+                [4, 3],
+                [5, 4],
+                [6, 5],
+                [7, 6],
+                [8, 7],
+                [9, 8],
+                [10, 9],
+                [11, 10],
+            ]);
             var result = preShearBPTree.shear(keep);
-            deepEqual(result.b_, [
+            deepEqual(result.tree.b_, [
                 1,
                 1,
                 1,
@@ -952,7 +976,7 @@ require(["jquery", "ByteArray", "BPTree"], function ($, ByteArray, BPTree) {
                 0,
                 0,
             ]);
-            deepEqual(result.names_, [
+            deepEqual(result.tree.names_, [
                 null,
                 "4",
                 "6",
@@ -965,19 +989,63 @@ require(["jquery", "ByteArray", "BPTree"], function ($, ByteArray, BPTree) {
                 "8",
                 "r",
             ]);
-            deepEqual(result.lengths_, [null, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
+            deepEqual(result.tree.lengths_, [
+                null,
+                2,
+                3,
+                4,
+                5,
+                6,
+                7,
+                8,
+                9,
+                10,
+                11,
+            ]);
+            deepEqual(result.shearedToFull, shearedToFull);
+            deepEqual(result.fullToSheared, fullToSheared);
 
             keep = new Set(["7", "10", "11"]);
+            shearedToFull = new Map([
+                [1, 6],
+                [2, 7],
+                [3, 8],
+                [4, 9],
+                [5, 10],
+                [6, 11],
+            ]);
+            fullToSheared = new Map([
+                [6, 1],
+                [7, 2],
+                [8, 3],
+                [9, 4],
+                [10, 5],
+                [11, 6],
+            ]);
             result = preShearBPTree.shear(keep);
-            deepEqual(result.b_, [1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0]);
-            deepEqual(result.names_, [null, "7", "10", "11", "9", "8", "r"]);
-            deepEqual(result.lengths_, [null, 6, 7, 8, 9, 10, 11]);
+            deepEqual(result.tree.b_, [1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0]);
+            deepEqual(result.tree.names_, [
+                null,
+                "7",
+                "10",
+                "11",
+                "9",
+                "8",
+                "r",
+            ]);
+            deepEqual(result.tree.lengths_, [null, 6, 7, 8, 9, 10, 11]);
+            deepEqual(result.shearedToFull, shearedToFull);
+            deepEqual(result.fullToSheared, fullToSheared);
 
             keep = new Set([]);
+            shearedToFull = new Map([[1, 11]]);
+            fullToSheared = new Map([[11, 1]]);
             result = preShearBPTree.shear(keep);
-            deepEqual(result.b_, [1, 0]);
-            deepEqual(result.names_, [null, "r"]);
-            deepEqual(result.lengths_, [null, 11]);
+            deepEqual(result.tree.b_, [1, 0]);
+            deepEqual(result.tree.names_, [null, "r"]);
+            deepEqual(result.tree.lengths_, [null, 11]);
+            deepEqual(result.shearedToFull, shearedToFull);
+            deepEqual(result.fullToSheared, fullToSheared);
         });
     });
 });
diff --git a/tests/test-tree-controller.js b/tests/test-tree-controller.js
new file mode 100644
index 000000000..c511a6260
--- /dev/null
+++ b/tests/test-tree-controller.js
@@ -0,0 +1,379 @@
+require(["jquery", "UtilitiesForTesting", "util", "TreeController"], function (
+    $,
+    UtilitiesForTesting,
+    util,
+    TreeController
+) {
+    $(document).ready(function () {
+        // Setup test variables
+        // Note: This is ran for each test() so tests can modify bpArray
+        // without affecting other tests.
+        module("TreeController", {
+            setup: function () {
+                this.tree = UtilitiesForTesting.getTestData(false).tree;
+                this.names = ["", "t1", "t2", "t3", "i4", "i5", "t6", "r"];
+                this.tree.names_ = this.names;
+                this.lengths = [null, 1, 2, 3, 4, 5, 6, null];
+                this.tree.lengths_ = this.lengths;
+                this.treeController = new TreeController(this.tree);
+            },
+
+            teardown: function () {
+                this.tree = null;
+                this.treeController = null;
+            },
+        });
+
+        test("Test shear", function () {
+            this.treeController.shear(new Set(["t2", "t3"]));
+
+            // checks to make sure correct names are kept
+            var shearNames = [null, "t2", "t3", "i4", "i5", "r"];
+            var resutlNames = this.treeController.model.shearedTree.names_;
+            deepEqual(resutlNames, shearNames);
+
+            var shearLengths = [null, 2, 3, 4, 5, null];
+            var resultLengts = this.treeController.model.shearedTree.lengths_;
+            deepEqual(resultLengts, shearLengths);
+
+            // checks to make sure structre of tree is correct
+            var shearTree = [1, 1, 1, 1, 0, 1, 0, 0, 0, 0];
+            var resultTree = this.treeController.model.shearedTree.b_;
+            deepEqual(resultTree, shearTree);
+
+            // checks to make sure the mappings from orignal tree to shear tree
+            // is correct and vice-versa
+            var fullToSheared = new Map([
+                [2, 1],
+                [3, 2],
+                [4, 3],
+                [5, 4],
+                [7, 5],
+            ]);
+            var shearedToFull = new Map([
+                [1, 2],
+                [2, 3],
+                [3, 4],
+                [4, 5],
+                [5, 7],
+            ]);
+            var resultOrigToCur = this.treeController.model.fullToSheared;
+            var resultCurToOrig = this.treeController.model.shearedToFull;
+            deepEqual(resultOrigToCur, fullToSheared);
+            deepEqual(resultCurToOrig, shearedToFull);
+        });
+
+        test("Test unshear", function () {
+            this.treeController.shear(new Set(["t2", "t3"]));
+            this.treeController.unshear();
+
+            deepEqual(this.treeController.model.shearedTree.names_, this.names);
+            deepEqual(
+                this.treeController.model.shearedTree.lengths_,
+                this.lengths
+            );
+
+            var map = new Map([
+                [1, 1],
+                [2, 2],
+                [3, 3],
+                [4, 4],
+                [5, 5],
+                [6, 6],
+                [7, 7],
+            ]);
+            deepEqual(this.treeController.model.shearedToFull, map);
+            deepEqual(this.treeController.model.fullToSheared, map);
+        });
+
+        test("Test postorderTraversal", function () {
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var nodes = [2, 3, 4, 5, 7];
+            var result = [
+                ...this.treeController.postorderTraversal((includeRoot = true)),
+            ];
+            deepEqual(result, nodes);
+
+            nodes.pop();
+            result = [
+                ...this.treeController.postorderTraversal(
+                    (includeRoot = false)
+                ),
+            ];
+            deepEqual(result, nodes);
+
+            this.treeController.unshear();
+            nodes = [1, 2, 3, 4, 5, 6, 7];
+            result = [
+                ...this.treeController.postorderTraversal((includeRoot = true)),
+            ];
+            deepEqual(result, nodes);
+        });
+
+        test("Test getLengthStats", function () {
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var stats = {
+                avg: 3.5,
+                min: 2,
+                max: 5,
+            };
+            var result = this.treeController.getLengthStats();
+            deepEqual(result, stats);
+
+            this.treeController.unshear();
+            stats = {
+                avg: 3.5,
+                min: 1,
+                max: 6,
+            };
+            result = this.treeController.getLengthStats();
+            deepEqual(result, stats);
+        });
+
+        test("Test name", function () {
+            // name() only uses the original tree and thus is not effected
+            // by the shear operation
+            var index = this.treeController.postorderselect(1);
+            var name = this.treeController.name(index);
+            deepEqual(name, "t1");
+        });
+
+        test("Test getAllNames", function () {
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var shearNames = ["t2", "t3", "i4", "i5", "r"];
+            var resutlNames = this.treeController.getAllNames();
+            deepEqual(resutlNames, shearNames);
+
+            this.treeController.unshear();
+            shearNames = ["t1", "t2", "t3", "i4", "i5", "t6", "r"];
+            deepEqual(this.treeController.getAllNames(), shearNames);
+        });
+
+        test("Test numleaves", function () {
+            this.treeController.shear(new Set(["t2", "t3"]));
+            equal(this.treeController.numleaves(), 2);
+
+            this.treeController.unshear();
+            equal(this.treeController.numleaves(), 4);
+        });
+
+        test("Test length", function () {
+            // length() only uses the original tree and thus is not effected
+            // by the shear operation
+            var index = this.treeController.postorderselect(1);
+            var length = this.treeController.length(index);
+            equal(length, 1);
+        });
+
+        test("Test parent", function () {
+            // parent() only uses the original tree and thus is not effected
+            // by the shear operation
+            var index = this.treeController.postorderselect(1);
+            var parent = this.treeController.parent(index);
+            parent = this.treeController.postorder(parent);
+            equal(parent, 5);
+        });
+
+        test("Test root", function () {
+            // root() only uses the original tree and thus is not effected
+            // by the shear operation
+            equal(this.treeController.root(), 0);
+        });
+
+        test("Test isleaf", function () {
+            // isleaf() only uses the original tree and thus is not effected
+            // by the shear operation
+            var index = this.treeController.postorderselect(1);
+            var isleaf = this.treeController.isleaf(index);
+            equal(isleaf, true);
+
+            index = this.treeController.postorderselect(7);
+            isleaf = this.treeController.isleaf(index);
+            equal(isleaf, false);
+        });
+
+        test("Test fchild", function () {
+            // fchild's input/output is in respect to the original tree.
+            // However, fchild will use the topology of the sheared tree.
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var index = this.treeController.postorderselect(5);
+            var fchild = this.treeController.fchild(index);
+            var expected = this.treeController.postorderselect(4);
+            equal(fchild, expected);
+
+            this.treeController.unshear();
+            index = this.treeController.postorderselect(5);
+            fchild = this.treeController.fchild(index);
+            expected = this.treeController.postorderselect(1);
+            equal(fchild, expected);
+        });
+
+        test("Test lchild", function () {
+            // lchild's input/output is in respect to the original tree.
+            // However, lchild will use the topology of the sheared tree.
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var index = this.treeController.postorderselect(7);
+            var lchild = this.treeController.lchild(index);
+            var expected = this.treeController.postorderselect(5);
+            equal(lchild, expected);
+
+            this.treeController.unshear();
+            index = this.treeController.postorderselect(7);
+            lchild = this.treeController.lchild(index);
+            expected = this.treeController.postorderselect(6);
+            equal(lchild, expected);
+        });
+
+        test("Test nsibling", function () {
+            // nsibling's input/output is in respect to the original tree.
+            // However, nsibling will use the topology of the sheared tree.
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var index = this.treeController.postorderselect(5);
+            var nsibling = this.treeController.nsibling(index);
+            var expected = 0; // doesn't have a next sibling
+            equal(nsibling, expected);
+
+            this.treeController.unshear();
+            index = this.treeController.postorderselect(5);
+            nsibling = this.treeController.nsibling(index);
+            expected = this.treeController.postorderselect(6);
+            equal(nsibling, expected);
+        });
+
+        test("Test psibling", function () {
+            // psibling's input/output is in respect to the original tree.
+            // However, psibling will use the topology of the sheared tree.
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var index = this.treeController.postorderselect(4);
+            var psibling = this.treeController.psibling(index);
+            var expected = 0; // doesn't have a next sibling
+            equal(psibling, expected);
+
+            this.treeController.unshear();
+            index = this.treeController.postorderselect(4);
+            psibling = this.treeController.psibling(index);
+            expected = this.treeController.postorderselect(1);
+            equal(psibling, expected);
+        });
+
+        test("Test postorder", function () {
+            // postorder only uses the original tree and thus is not effected
+            // by the shear operation
+            var index = this.treeController.postorderselect(1);
+            var postorder = this.treeController.postorder(index);
+            equal(postorder, 1);
+        });
+
+        test("Test postorderselect", function () {
+            // postorderselect only uses the original tree and thus is not effected
+            // by the shear operation
+            var index = this.treeController.postorderselect(1);
+            equal(index, 2);
+        });
+
+        test("Test preorder", function () {
+            // preorder only uses the original tree and thus is not effected
+            // by the shear operation
+            var index = this.treeController.preorderselect(1);
+            var preorder = this.treeController.preorder(index);
+            equal(preorder, 1);
+        });
+
+        test("Test preorderselect", function () {
+            // preorderselect only uses the original tree and thus is not effected
+            // by the shear operation
+            var index = this.treeController.preorderselect(1);
+            equal(index, 0);
+        });
+
+        test("Test inOrderTraversal", function () {
+            // inOrderTraversal's input/output is in respect to the original tree.
+            // However, inOrderTraversal will use the topology of the sheared tree.
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var expected = [7, 5, 4, 2, 3];
+            var result = [
+                ...this.treeController.inOrderTraversal((includeRoot = true)),
+            ];
+            deepEqual(result, expected);
+            expected.shift();
+            result = [
+                ...this.treeController.inOrderTraversal((includeRoot = false)),
+            ];
+            deepEqual(result, expected);
+
+            this.treeController.unshear();
+            expected = [7, 5, 6, 1, 4, 2, 3];
+            result = [
+                ...this.treeController.inOrderTraversal((includeRoot = true)),
+            ];
+            deepEqual(result, expected);
+            expected.shift();
+            result = [
+                ...this.treeController.inOrderTraversal((includeRoot = false)),
+            ];
+            deepEqual(result, expected);
+        });
+
+        test("Test getTotalLength", function () {
+            // getTotalLength's input/output is in respect to the original tree.
+            // However, getTotalLength will use the topology of the sheared tree.
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var result = this.treeController.getTotalLength(2, 7);
+            equal(result, 11);
+
+            this.treeController.unshear();
+            result = this.treeController.getTotalLength(2, 7);
+            equal(result, 11);
+        });
+
+        test("Test findTips", function () {
+            // findTips's input/output is in respect to the original tree.
+            // However, findTips will use the topology of the sheared tree.
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var result = this.treeController.findTips(5);
+            deepEqual(result, [2, 3]);
+
+            this.treeController.unshear();
+            result = this.treeController.findTips(5);
+            deepEqual(result, [1, 2, 3]);
+        });
+
+        test("Test getNumTips", function () {
+            // getNumTips's input/output is in respect to the original tree.
+            // However, getNumTips will use the topology of the sheared tree.
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var result = this.treeController.getNumTips(5);
+            deepEqual(result, 2);
+
+            this.treeController.unshear();
+            result = this.treeController.getNumTips(5);
+            deepEqual(result, 3);
+        });
+
+        test("Test containsNode", function () {
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var result = this.treeController.containsNode("t1");
+            equal(result, false);
+
+            this.treeController.unshear();
+            result = this.treeController.containsNode("t1");
+            equal(result, true);
+        });
+
+        test("Test getNodesWithName", function () {
+            // getNodesWithName's input/output is in respect to the original tree.
+            // However, getNodesWithName will use the topology of the sheared tree.
+            this.treeController.shear(new Set(["t2", "t3"]));
+            var result = this.treeController.getNodesWithName("t2");
+            deepEqual(result, [2]);
+            result = this.treeController.getNodesWithName("t1");
+            deepEqual(result, []);
+
+            this.treeController.unshear();
+            result = this.treeController.getNodesWithName("t2");
+            deepEqual(result, [2]);
+            result = this.treeController.getNodesWithName("t1");
+            deepEqual(result, [1]);
+        });
+    });
+});