Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ignoring branch lengths during layout; update tree properties panel UI; some minor clade collapsing changes #363

Merged
merged 14 commits into from
Sep 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions empress/support_files/css/empress.css
Original file line number Diff line number Diff line change
Expand Up @@ -679,3 +679,10 @@ p.side-header button:hover,
.selected-metadata-choice {
background-color: #333 !important;
}

.side-panel-inner-header {
font-weight: bold;
justify-content: center !important;
margin-top: 0;
margin-bottom: 0;
}
10 changes: 7 additions & 3 deletions empress/support_files/js/bp-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -629,15 +629,19 @@ define(["ByteArray", "underscore"], function (ByteArray, _) {
*
* @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
*/
BPTree.prototype.getTotalLength = function (start, end) {
BPTree.prototype.getTotalLength = function (start, end, ignoreLengths) {
fedarko marked this conversation as resolved.
Show resolved Hide resolved
var curNode = start;
var totalLength = 0;
while (curNode !== end) {
totalLength += this.length(this.postorderselect(curNode));
curNode = this.parent(this.postorderselect(curNode));
var popos = this.postorderselect(curNode);
var nodeLen = ignoreLengths ? 1 : this.length(popos);
totalLength += nodeLen;
curNode = this.parent(popos);
if (curNode === -1) {
throw "Node " + start + " must be a descendant of " + end;
}
Expand Down
129 changes: 79 additions & 50 deletions empress/support_files/js/empress.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ define([
*/
this.ignoreAbsentTips = true;

/**
* @type{Bool}
* Whether to ignore node lengths during layout or not
*/
this.ignoreLengths = false;

/**
* @type{CanvasEvents}
* Handles user events
Expand Down Expand Up @@ -303,7 +309,7 @@ define([
*
* This stores the group membership of a node. -1 means the node doesn't
* belong to a group. This array is used to collapse clades by search
* for clades in this array that share the same group membershi[.
* for clades in this array that share the same group membership.
*/
this._group = new Array(this._tree.size + 1).fill(-1);
}
Expand All @@ -317,7 +323,12 @@ define([
var data, i;
// Rectangular
if (this._currentLayout === "Rectangular") {
data = LayoutsUtil.rectangularLayout(this._tree, 4020, 4020);
data = LayoutsUtil.rectangularLayout(
this._tree,
4020,
4020,
this.ignoreLengths
);
this._yrscf = data.yScalingFactor;
for (i = 1; i <= this._tree.size; i++) {
// remove old layout information
Expand All @@ -332,7 +343,12 @@ define([
data.lowestChildYr[i];
}
} else if (this._currentLayout === "Circular") {
data = LayoutsUtil.circularLayout(this._tree, 4020, 4020);
data = LayoutsUtil.circularLayout(
this._tree,
4020,
4020,
this.ignoreLengths
);
for (i = 1; i <= this._tree.size; i++) {
// remove old layout information
this._treeData[i].length = this._numNonLayoutParams;
Expand All @@ -351,7 +367,12 @@ define([
data.arcEndAngle[i];
}
} else {
data = LayoutsUtil.unrootedLayout(this._tree, 4020, 4020);
data = LayoutsUtil.unrootedLayout(
this._tree,
4020,
4020,
this.ignoreLengths
);
for (i = 1; i <= this._tree.size; i++) {
// remove old layout information
this._treeData[i].length = this._numNonLayoutParams;
Expand Down Expand Up @@ -2199,6 +2220,38 @@ define([
return Object.keys(this._layoutToCoordSuffix);
};

/**
* Redraws the tree, using the current layout and any layout parameters
* that may have changed in the interim.
*/
Empress.prototype.reLayout = function () {
this.getLayoutInfo();

// recollapse clades
if (Object.keys(this._collapsedClades).length != 0) {
this._collapsedCladeBuffer = [];
this.collapseClades();
}

// Adjust the thick-line stuff before calling drawTree() --
// this will get the buffer set up before it's actually drawn
// in drawTree(). Doing these calls out of order (draw tree,
// then call thickenColoredNodes()) causes the thick-line
// stuff to only change whenever the tree is redrawn.
this.thickenColoredNodes(this._currentLineWidth);

// Undraw or redraw barplots as needed
var supported = this._barplotPanel.updateLayoutAvailability(
this._currentLayout
);
if (!supported && this._barplotsDrawn) {
this.undrawBarplots();
} else if (supported && this._barplotPanel.enabled) {
this.drawBarplots(this._barplotPanel.layers);
}
this.drawTree();
};

/**
* Redraws the tree with a new layout (if different from current layout).
*/
Expand All @@ -2207,34 +2260,11 @@ define([
if (this._layoutToCoordSuffix.hasOwnProperty(newLayout)) {
// get new layout
this._currentLayout = newLayout;
this.getLayoutInfo();

// recollapse clades
if (Object.keys(this._collapsedClades).length != 0) {
this._collapsedCladeBuffer = [];
this.collapseClades();
}

// Adjust the thick-line stuff before calling drawTree() --
// this will get the buffer set up before it's actually drawn
// in drawTree(). Doing these calls out of order (draw tree,
// then call thickenColoredNodes()) causes the thick-line
// stuff to only change whenever the tree is redrawn.
this.thickenColoredNodes(this._currentLineWidth);

// Undraw or redraw barplots as needed
var supported = this._barplotPanel.updateLayoutAvailability(
newLayout
);
// TODO: don't call drawTree() from either of these barplot
// funcs, since it'll get called in centerLayoutAvgPoint anyway
if (!supported && this._barplotsDrawn) {
this.undrawBarplots();
} else if (supported && this._barplotPanel.enabled) {
this.drawBarplots(this._barplotPanel.layers);
}
this.reLayout();
// recenter viewing window
// Note: this function calls drawTree()
// NOTE: this function calls drawTree(), which is redundant
// since reLayout() already called it. Would be good to
// minimize redundant calls to that.
fedarko marked this conversation as resolved.
Show resolved Hide resolved
this.centerLayoutAvgPoint();
} else {
// This should never happen under normal circumstances (the
Expand Down Expand Up @@ -2403,19 +2433,16 @@ define([
/**
* Collapses all clades that share the same color into a quadrilateral.
*
* Note: if a clade contains a node with DEFAULT_COLOR it will not be
* collapsed
* NOTE: Previously, this checked this._collapsedClades to see if there
* were any "cached" clades. I've removed this for now because it's
* possible for the layout to stay the same but the clades still to
* need updating (e.g. if the "ignore lengths" setting of Empress
* changes). If collapsing clades is a bottleneck, we could try to add
* back caching.
*
* @return{Boolean} true if at least one clade was collapse. false otherwise
*/
Empress.prototype.collapseClades = function () {
// first check if any collapsed clades have been cached
if (Object.keys(this._collapsedClades).length != 0) {
for (var cladeRoot in this._collapsedClades) {
this.createCollapsedCladeShape(cladeRoot);
}
return;
}
// The following algorithm consists of two parts: 1) find all clades
// whose member nodes have the same color, 2) collapse the clades

Expand Down Expand Up @@ -2451,8 +2478,6 @@ define([
// collaped.
// Collapsing a clade will set the .visible property of members to
// false and will then be skipped in the for loop.
// Note: if the root of a clade has DEFUALT_COLOR then it will not be
// collapsed (since all of its children will also have DEFAULT_COLOR)
var inorder = this._tree.inOrderNodes();
for (var node in inorder) {
node = inorder[node];
Expand Down Expand Up @@ -2631,8 +2656,8 @@ define([
* Collapse the clade at rootNode
*
* This method will set the .visible property for all nodes in the clade
* (execpt the root) to false. Also, the color of rootNode will be set to
* DEFAULT_COLOR and this._collapsedCladeBuffer will be modified.
* (except the root) to false. Also, this._collapsedCladeBuffer will be
* updated.
*
* Note: This method will cache the clade information. So, as long as
* the collapsed clades aren't changed, you do not need to call this
Expand Down Expand Up @@ -2669,7 +2694,11 @@ define([
left: cladeNodes[0],
right: cladeNodes[0],
deepest: cladeNodes[0],
length: this._tree.getTotalLength(cladeNodes[0], rootNode),
length: this._tree.getTotalLength(
cladeNodes[0],
rootNode,
this.ignoreLengths
),
color: this.getNodeInfo(rootNode, "color"),
};

Expand All @@ -2687,7 +2716,11 @@ define([
var curLeft = currentCladeInfo.left;
var curRight = currentCladeInfo.right;
var curDeep = currentCladeInfo.deepest;
var length = this._tree.getTotalLength(cladeNode, rootNode);
var length = this._tree.getTotalLength(
cladeNode,
rootNode,
this.ignoreLengths
);

// update deepest node
if (length > currentCladeInfo.length) {
Expand Down Expand Up @@ -2723,10 +2756,6 @@ define([

// step 4)
this.createCollapsedCladeShape(rootNode);

// We set the root of the clade to default otherwise, the branch that
// connects the root clade to its parent will still be colored
this.setNodeInfo(rootNode, "color", this.DEFAULT_COLOR);
};

/**
Expand Down
Loading