-
Notifications
You must be signed in to change notification settings - Fork 31
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
Shear UI #505
Shear UI #505
Changes from 19 commits
cbf4060
227ec29
66c2e56
1183aa7
eafbea3
7301e82
888cf72
f4f668f
a408326
b043826
caa916e
f598928
5ca5809
89a4326
654df69
d6aa515
de01d93
fcb6b5b
55f6f7e
b3acbd1
263dd6a
5bb8729
68d0d63
603bf97
31b5af3
7f56e68
9a9bb75
9dc26bc
4445ebd
15f81ab
4630689
73892fd
3ca595e
66e5ded
9eb4243
e3d128f
4b4d8e3
3d05f17
d909e83
3b6d854
0803dc0
cc754e9
3a2eede
95392a5
45e2d49
3570a19
e78ecd1
c95adf8
af71569
35a2aa9
928a2ce
6d4571a
6074e5d
0b081ca
0bd1160
d3c2a61
ac544fb
55ca475
aedb860
90571be
149ecb5
288a140
7faa70a
cb085ba
8af9b93
c0fbd57
0505705
924b938
0bae2ce
953abd8
8767132
01d7022
c57a5d2
00b7dee
b664bfd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -391,21 +391,32 @@ define([ | |
); | ||
// Rectangular | ||
if (this._currentLayout === "Rectangular") { | ||
data = LayoutsUtil.rectangularLayout( | ||
this._tree.getTree(), | ||
4020, | ||
4020, | ||
// since lengths for "ignoreLengths" are set by `lengthGetter`, | ||
// we don't need (and should likely deprecate) the ignoreLengths | ||
// option for the Layout functions since the layout function only | ||
// needs to know lengths in order to layout a tree, it doesn't | ||
// really need encapsulate all of the logic for determining | ||
// what lengths it should lay out. | ||
this.leafSorting, | ||
undefined, | ||
lengthGetter, | ||
checkLengthsChange | ||
); | ||
// tree is just root | ||
kwcantrell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (this._tree.currentSize == 1) { | ||
data = { | ||
xCoord: [null, 0], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To avoid rewriting ...Although, if we are going to disable shearing all tips from the tree, then I guess we could just remove this code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of setting the value of each component, I create the following helper function:
|
||
yCoord: [null, 0], | ||
highestChildYr: [null, 0], | ||
lowestChildYr: [null, 0], | ||
yScalingFactor: [null, 0], | ||
}; | ||
} else { | ||
data = LayoutsUtil.rectangularLayout( | ||
this._tree.getTree(), | ||
4020, | ||
4020, | ||
// since lengths for "ignoreLengths" are set by `lengthGetter`, | ||
// we don't need (and should likely deprecate) the ignoreLengths | ||
// option for the Layout functions since the layout function only | ||
// needs to know lengths in order to layout a tree, it doesn't | ||
// really need encapsulate all of the logic for determining | ||
// what lengths it should lay out. | ||
this.leafSorting, | ||
undefined, | ||
lengthGetter, | ||
checkLengthsChange | ||
); | ||
} | ||
this._yrscf = data.yScalingFactor; | ||
for (i of this._tree.postorderTraversal((includeRoot = true))) { | ||
// remove old layout information | ||
|
@@ -421,15 +432,30 @@ define([ | |
j += 1; | ||
} | ||
} else if (this._currentLayout === "Circular") { | ||
data = LayoutsUtil.circularLayout( | ||
this._tree.getTree(), | ||
4020, | ||
4020, | ||
this.leafSorting, | ||
undefined, | ||
lengthGetter, | ||
checkLengthsChange | ||
); | ||
if (this._tree.currentSize == 1) { | ||
data = { | ||
// store new layout information | ||
x0: [null, 0], | ||
y0: [null, 0], | ||
x1: [null, 0], | ||
y1: [null, 0], | ||
angle: [null, 0], | ||
arcx0: [null, 0], | ||
arcy0: [null, 0], | ||
arcStartAngle: [null, 0], | ||
arcEndAngle: [null, 0], | ||
}; | ||
} else { | ||
data = LayoutsUtil.circularLayout( | ||
this._tree.getTree(), | ||
4020, | ||
4020, | ||
this.leafSorting, | ||
undefined, | ||
lengthGetter, | ||
checkLengthsChange | ||
); | ||
} | ||
for (i of this._tree.postorderTraversal((includeRoot = true))) { | ||
// remove old layout information | ||
this._treeData[i].length = this._numOfNonLayoutParams; | ||
|
@@ -449,14 +475,22 @@ define([ | |
j += 1; | ||
} | ||
} else { | ||
data = LayoutsUtil.unrootedLayout( | ||
this._tree.getTree(), | ||
4020, | ||
4020, | ||
undefined, | ||
lengthGetter, | ||
checkLengthsChange | ||
); | ||
if (this._tree.currentSize == 1) { | ||
data = { | ||
// store new layout information | ||
xCoord: [null, 0], | ||
yCoord: [null, 0], | ||
}; | ||
} else { | ||
data = LayoutsUtil.unrootedLayout( | ||
this._tree.getTree(), | ||
4020, | ||
4020, | ||
undefined, | ||
lengthGetter, | ||
checkLengthsChange | ||
); | ||
} | ||
for (i of this._tree.postorderTraversal((includeRoot = true))) { | ||
// remove old layout information | ||
this._treeData[i].length = this._numOfNonLayoutParams; | ||
|
@@ -2142,6 +2176,18 @@ define([ | |
this.drawTree(); | ||
}; | ||
|
||
/** | ||
* | ||
*/ | ||
Empress.prototype.getUniqueSampleMetadataInfo = function (cat) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know you mentioned docs were a work in progress, but this function in particular could really use some details -- I am unsure why exactly this has to be used in place of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree documentation is definitely needed! Reason this is used in place of |
||
var obs = this._biom.getObsBy(cat); | ||
var nodes = new Set([...this._tree.postorderTraversal()]); | ||
_.each(obs, function (observations, key) { | ||
obs[key] = observations.filter((x) => nodes.has(x)); | ||
}); | ||
kwcantrell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return obs; | ||
}; | ||
|
||
/** | ||
* Color the tree using sample metadata | ||
* | ||
|
@@ -2162,7 +2208,8 @@ define([ | |
reverse = false | ||
) { | ||
var tree = this._tree; | ||
var obs = this._biom.getObsBy(cat); | ||
// var obs = this._biom.getObsBy(cat); | ||
kwcantrell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var obs = this.getUniqueSampleMetadataInfo(cat); | ||
var categories = Object.keys(obs); | ||
|
||
// Assign colors to categories | ||
|
@@ -2177,6 +2224,11 @@ define([ | |
var cm = colorer.getMapRGB(); | ||
// colors for the legend | ||
var keyInfo = colorer.getMapHex(); | ||
for (var key in keyInfo) { | ||
if (obs[key].length === 0) { | ||
delete keyInfo[key]; | ||
} | ||
} | ||
fedarko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// shared by the following for loops | ||
var i, j, category; | ||
|
@@ -2232,6 +2284,9 @@ define([ | |
* an array of the node name(s) with each value. | ||
*/ | ||
Empress.prototype.getUniqueFeatureMetadataInfo = function (cat, method) { | ||
// get nodes in tree | ||
var nodes = new Set([...this._tree.postorderTraversal()]); | ||
|
||
// In order to access feature metadata for a given node, we need to | ||
// find the 0-based index in this._featureMetadataColumns that the | ||
// specified f.m. column corresponds to. (We *could* get around this by | ||
|
@@ -2258,15 +2313,16 @@ define([ | |
var uniqueValueToFeatures = {}; | ||
_.each(fmObjs, function (mObj) { | ||
_.mapObject(mObj, function (fmRow, node) { | ||
var fmVal = fmRow[fmIdx]; | ||
if (!_.has(uniqueValueToFeatures, fmVal)) { | ||
uniqueValueToFeatures[fmVal] = []; | ||
} | ||
// need to convert to integer | ||
node = parseInt(node); | ||
// This is loosely based on how BIOMTable.getObsBy() works. | ||
var fmVal = fmRow[fmIdx]; | ||
if (_.has(uniqueValueToFeatures, fmVal)) { | ||
uniqueValueToFeatures[fmVal].push(node); | ||
} else { | ||
uniqueValueToFeatures[fmVal] = [node]; | ||
if (!nodes.has(node)) { | ||
return; | ||
} | ||
uniqueValueToFeatures[fmVal].push(node); | ||
}); | ||
}); | ||
|
||
|
@@ -2329,9 +2385,14 @@ define([ | |
); | ||
// colors for drawing the tree | ||
var cm = colorer.getMapRGB(); | ||
|
||
// colors for the legend | ||
var keyInfo = colorer.getMapHex(); | ||
|
||
for (var key in keyInfo) { | ||
if (uniqueValueToFeatures[key].length === 0) { | ||
delete keyInfo[key]; | ||
} | ||
} | ||
// Do upwards propagation only if the coloring method is "tip" | ||
if (method === "tip") { | ||
obs = this._projectObservations(obs, false); | ||
|
@@ -3565,5 +3626,63 @@ define([ | |
} | ||
}; | ||
|
||
/** | ||
* This will shear/unshear | ||
*/ | ||
Empress.prototype.shear = function (shearMap) { | ||
this._tree.unshear(); | ||
var scope = this; | ||
var removeNodes = new Set(); | ||
|
||
shearMap.forEach(function (values, cat) { | ||
var fmInfo = scope.getUniqueFeatureMetadataInfo(cat, "tip"); | ||
var uniqueValueToFeatures = fmInfo.uniqueValueToFeatures; | ||
_.each(values, function (val) { | ||
var obs = uniqueValueToFeatures[val]; | ||
for (var node of obs) { | ||
removeNodes.add(node); | ||
} | ||
}); | ||
}); | ||
|
||
// remove removeNodes | ||
var allNodes = Array.from(Array(this._tree.size + 1).keys()); | ||
kwcantrell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
allNodes.shift(); | ||
|
||
allNodes = new Set(allNodes); | ||
var keepNodes = new Set( | ||
[...allNodes].filter((x) => !removeNodes.has(x)) | ||
); | ||
|
||
var keepNames = []; | ||
for (var node of keepNodes) { | ||
var name = this._tree.name(this._tree.postorderselect(node)); | ||
keepNames.push(name); | ||
} | ||
|
||
this._tree.shear(new Set(keepNames)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about adding a method to the TreeController class that takes in a map and shears the tree. For example There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not entirely sure what you mean here. @ElDeveloper do you mind elaborating a bit more? What exactly is the map in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I meant is that some of the shearing functionality in If this doesn't make sense, let me know and we can hop on a quick call. |
||
var nodeNames = this._tree.getAllNames(); | ||
// Don't include nodes with the name null (i.e. nodes without a | ||
// specified name in the Newick file) in the auto-complete. | ||
nodeNames = nodeNames.filter((n) => n !== null); | ||
this._events.autocomplete(nodeNames); | ||
|
||
kwcantrell marked this conversation as resolved.
Show resolved
Hide resolved
fedarko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.getLayoutInfo(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like this is duplicated code from |
||
|
||
// Undraw or redraw barplots as needed (assuming barplots are supported | ||
// in the first place, of course; if no feature or sample metadata at | ||
// all was passed then barplots are not available :() | ||
if (!_.isNull(this._barplotPanel)) { | ||
var supported = this._barplotPanel.updateLayoutAvailability( | ||
this._currentLayout | ||
); | ||
if (!supported && this._barplotsDrawn) { | ||
this.undrawBarplots(); | ||
} else if (supported && this._barplotPanel.enabled) { | ||
this.drawBarplots(); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like this is duplicated code from |
||
}; | ||
|
||
return Empress; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -218,17 +218,8 @@ define(["jquery", "underscore", "util"], function ($, _, util) { | |
* @param {Object} info Color key information. This should map unique | ||
* values (e.g. in sample or feature metadata) to | ||
* their assigned color, expressed in hex format. | ||
* | ||
* @throws {Error} If info has no keys. This check is done before anything | ||
* else is done in this function. | ||
*/ | ||
Legend.prototype.addCategoricalKey = function (name, info) { | ||
if (_.isEmpty(info)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is the error check being removed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
throw new Error( | ||
"Can't create a categorical legend when there are no " + | ||
"categories in the info" | ||
); | ||
} | ||
this.clear(); | ||
this.addTitle(name); | ||
this._sortedCategories = util.naturalSort(_.keys(info)); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason why this class is duplicated compared to
barplot-layer-legend
I notice there's adiv
filter but any reason why these need to be two different classes?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an overflow occurs I wanted the title of the filter to always be in view. For example if you select "Level 7" for the shear filter, you can scroll and always see "Level 7". This occurs because
.shear-layey-legend
only applies todiv
elements within theshear-legends
container. If I usedbarplot-layer-legend
then the title of the filter would also scroll and if I added div tobarplot-layer-legend
then it would break the barplot legend.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's probably a better solution but that is what was easiest for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A simple-ish solution might be creating a title element for each shear layer (for example, the
Layer N
text in barplot layers), and then not having a title at all of the stuff in the shear layer legend. Since each feature metadata column can correspond to at most one shear layer, the title we assign a shear layer could beShear Layer: Level 4
or something like that?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kwcantrell Would it be possible to make this change? (edit - whoops, looks like github lost track of the earlier comments -- see parent at https://github.com/biocore/empress/pull/505/files#r603485148)
I don't think it should require much if any additional work, and should reduce the amount of code complexity -- I think it would let us remove this duplicated CSS code.
This would involve storing the "Level N" text in an element with
<h3 style="text-align: center;">
instead of in the legend. The code for this is adaptable from the barplot layer JS here.We could also store the select / unselect all buttons in a
<p>
underneath this h3 tag, with the same style as the feature / sample metadata barplot toggle buttons (see code here). This should make the UI look nicer and would be more consistent with the barplot stuff, I think?I made these changes in a local copy of the repo in a few minutes of editing, and the UI now looks as follows. It's a small change but I think the consistency will help users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make things simpler, I just added the title/buttons to the layer div rather than the legend div. The result gives the same effect but changes less code (and we can still remove the duplicated css).