Skip to content

Commit 707bfc5

Browse files
committed
Merge branch 'colormode-list'
2 parents a4b3f3f + c78acc7 commit 707bfc5

File tree

3 files changed

+284
-1
lines changed

3 files changed

+284
-1
lines changed

app/index.html

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,60 @@ <h5 class="mb-0">
365365
</div>
366366
</div>
367367

368+
<div class="card">
369+
<div class="card-header" id="headingColor">
370+
<h5 class="mb-0">
371+
<button
372+
class="btn btn-link collapsed"
373+
data-toggle="collapse"
374+
data-target="#collapseColor"
375+
aria-expanded="false"
376+
aria-controls="collapseColor"
377+
>
378+
Color
379+
</button>
380+
</h5>
381+
</div>
382+
<div
383+
id="collapseColor"
384+
class="collapse"
385+
aria-labelledby="headingColor"
386+
data-parent="#accordion"
387+
>
388+
<div class="card-body">
389+
<!-- <div class="form-group">
390+
<label for="colorMode">Color Mode</label>
391+
<select id="colorMode" class="form-control form-control-sm skip"></select>
392+
</div> -->
393+
<div class="form-group">
394+
<label>Default Color</label>
395+
<input
396+
type="color"
397+
id="defaultColor"
398+
class="skip"
399+
value="#4682B4"
400+
>
401+
</div>
402+
<div class="form-group">
403+
<label>Highlight Color</label>
404+
<input
405+
type="color"
406+
id="highlightColor"
407+
class="skip"
408+
value="#feb640"
409+
>
410+
</div>
411+
<div class="form-group">
412+
<div class="switch">
413+
<label>
414+
<input id="highlightLeaves" type="checkbox" class="skip"> Highlight Leaves
415+
</label>
416+
</div>
417+
</div>
418+
</div>
419+
</div>
420+
</div>
421+
368422
<div class="card">
369423
<div class="card-header" id="headingMeta">
370424
<h5 class="mb-0">
@@ -603,7 +657,7 @@ <h5 class="modal-title" id="exportModalLabel">Export</h5>
603657
fetch("life.nwk").then(response => response.text().then(buildTree));
604658
});
605659

606-
["layout", "mode", "type"].forEach(thing => {
660+
["layout", "mode", "type", "colorMode"].forEach(thing => {
607661
var title = "valid" + thing[0].toUpperCase() + thing.slice(1) + "s";
608662
d3.select("#" + thing)
609663
.selectAll("option")
@@ -627,6 +681,12 @@ <h5 class="modal-title" id="exportModalLabel">Export</h5>
627681
layout: d3.select("#layout").node().value,
628682
mode: d3.select("#mode").node().value,
629683
type: d3.select("#type").node().value,
684+
colorOptions:
685+
{
686+
colorMode: "none",
687+
defaultColor: d3.select("#defaultColor").node().value,
688+
highlightColor: d3.select("#highlightColor").node().value,
689+
},
630690
leafNodes: d3.select("#leafNodes").node().checked,
631691
branchNodes: d3.select("#branchNodes").node().checked,
632692
leafLabels: d3.select("#leafLabels").node().checked,
@@ -672,6 +732,59 @@ <h5 class="modal-title" id="exportModalLabel">Export</h5>
672732
tree.setAnimation(cached);
673733
});
674734

735+
// might want this back when we have more color mode options
736+
// for now, ill leave it out in favor of the highlight toggle
737+
/* d3.select("#colorMode").on("input", function() {
738+
// this could prove annoying, but ill leave it for now
739+
if (this.value === "list") {
740+
d3.select("#defaultColor").node().value = "#243127";
741+
} else {
742+
d3.select("#defaultColor").node().value = "#4682B4";
743+
}
744+
745+
tree.setColorOptions({
746+
colorMode: this.value,
747+
defaultColor: d3.select("#defaultColor").node().value,
748+
highlightColor: d3.select("#highlightColor").node().value
749+
});
750+
});
751+
*/
752+
d3.select("#defaultColor").on("input", function() {
753+
tree.setColorOptions({
754+
colorMode: d3.select("#highlightLeaves").node().value ? "list" : "none",
755+
defaultColor: this.value,
756+
highlightColor: d3.select("#highlightColor").node().value,
757+
nodeList: tree.getNodeGUIDs(true)
758+
});
759+
});
760+
761+
d3.select("#highlightColor").on("input", function() {
762+
tree.setColorOptions({
763+
colorMode: d3.select("#highlightLeaves").node().value ? "list" : "none",
764+
defaultColor: d3.select("#defaultColor").node().value,
765+
highlightColor: this.value,
766+
nodeList: tree.getNodeGUIDs(true)
767+
});
768+
});
769+
770+
d3.select("#highlightLeaves").on("input", function() {
771+
console.log("tree", tree.getNodeGUIDs(true));
772+
if (this.value) {
773+
tree.setColorOptions({
774+
colorMode: "list",
775+
defaultColor: d3.select("#defaultColor").node().value,
776+
highlightColor: d3.select("#highlightColor").node().value,
777+
nodeList: tree.getNodeGUIDs(true)
778+
})
779+
} else {
780+
tree.setColorOptions({
781+
colorMode: "none",
782+
defaultColor: d3.select("#defaultColor").node().value,
783+
highlightColor: d3.select("#highlightColor").node().value
784+
})
785+
}
786+
});
787+
675788
d3.select("#branchNodeSize").on("input", function() {
676789
tree.eachBranchNode((node, data) => {
677790
d3.select(node).attr("r", this.value);

dist/tidytree.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,7 @@ var TidyTree = (function () {
12581258
layout: "vertical",
12591259
type: "tree",
12601260
mode: "smooth",
1261+
colorOptions: { colorMode: "none" },
12611262
leafNodes: true,
12621263
leafLabels: false,
12631264
equidistantLeaves: false,
@@ -1352,6 +1353,12 @@ var TidyTree = (function () {
13521353
*/
13531354
TidyTree.validModes = ["smooth", "square", "straight"];
13541355

1356+
/**
1357+
* The available color modes for rendering nodes.
1358+
* @type {Array}
1359+
*/
1360+
TidyTree.validColorModes = ["none", "list"]; // later, highlight on hover, or maybe color by annotation on a node/ search
1361+
13551362
/**
13561363
* Draws a Phylogenetic on the element referred to by selector
13571364
* @param {String} selector A CSS selector
@@ -1606,6 +1613,30 @@ var TidyTree = (function () {
16061613

16071614
nodeTransformers.dendrogram = nodeTransformers.tree;
16081615

1616+
/**
1617+
* Finds the color of a given node based on the color options provided.
1618+
*
1619+
* @param {Object} node - The node for which to find the color.
1620+
* @param {Object} colorOptions - The color options object containing the color mode, node list, default color, and highlight color.
1621+
* @return {string} The color of the node.
1622+
*/
1623+
function findNodeColor(node, colorOptions) {
1624+
if (colorOptions.colorMode === "none") {
1625+
// steelblue
1626+
return colorOptions.defaultColor ?? "#4682B4";
1627+
}
1628+
1629+
let nodeList = colorOptions.nodeList;
1630+
1631+
if (nodeList.includes(node.data._guid)) {
1632+
// charcoal
1633+
return colorOptions.highlightColor ?? "#feb640";
1634+
} else {
1635+
// yellowish
1636+
return colorOptions.defaultColor ?? "#243127";
1637+
}
1638+
}
1639+
16091640
const radToDeg = 180 / Math.PI;
16101641

16111642
let labelTransformers = {
@@ -1833,6 +1864,7 @@ var TidyTree = (function () {
18331864
(d.children && this.branchNodes) ||
18341865
(!d.children && this.leafNodes) ? 1 : 0
18351866
)
1867+
.style("fill", d => findNodeColor(d, this.colorOptions))
18361868
.on("mouseenter focusin", d => this.trigger("showtooltip", d))
18371869
.on("mouseout focusout", d => this.trigger("hidetooltip", d))
18381870
.on("contextmenu", d => this.trigger("contextmenu", d))
@@ -1884,6 +1916,9 @@ var TidyTree = (function () {
18841916
.duration(this.animation)
18851917
.attr("transform", nodeTransformer);
18861918

1919+
let nodeGlyphs = update.select("circle");
1920+
nodeGlyphs.style("fill", d => findNodeColor(d, this.colorOptions));
1921+
18871922
let nodeLabels = update.select("text");
18881923
if (this.layout === "vertical") {
18891924
nodeLabels
@@ -2013,6 +2048,28 @@ var TidyTree = (function () {
20132048
return this;
20142049
};
20152050

2051+
/**
2052+
* Set the TidyTree's colorOptions
2053+
* @param {Object} newColorOptions The new colorOptions
2054+
* @return {TidyTree} The TidyTree Object
2055+
*/
2056+
TidyTree.prototype.setColorOptions = function (newColorOptions) {
2057+
if (!TidyTree.validColorModes.includes(newColorOptions.colorMode)) {
2058+
throw Error(`
2059+
Cannot set TidyTree to colorOptions: ${newColorOptions.colorMode}\n
2060+
Valid colorModes are: ${TidyTree.validColorModes.join(', ')}
2061+
`);
2062+
}
2063+
if (newColorOptions.colorMode === 'list') {
2064+
if (!Array.isArray(newColorOptions.nodeList)) {
2065+
throw Error('nodeList must be an array for colorMode "list"');
2066+
}
2067+
}
2068+
this.colorOptions = newColorOptions;
2069+
if (this.parent) return this.redraw();
2070+
return this;
2071+
};
2072+
20162073
/**
20172074
* Set the TidyTree's mode
20182075
* @param {String} newMode The new mode
@@ -2339,6 +2396,34 @@ var TidyTree = (function () {
23392396
return this;
23402397
};
23412398

2399+
/**
2400+
* Retrieves the GUIDs of the nodes in the TidyTree instance.
2401+
*
2402+
* @param {boolean} leavesOnly - Whether to retrieve GUIDs only for leaf nodes.
2403+
* @return {Array} An array of GUIDs of the nodes.
2404+
*/
2405+
TidyTree.prototype.getNodeGUIDs = function (leavesOnly) {
2406+
// todo: make sure these are returned in order
2407+
let nodeList = this.parent
2408+
.select("svg")
2409+
.selectAll("g.tidytree-node-leaf circle")
2410+
._groups[0];
2411+
2412+
if (!leavesOnly) {
2413+
nodeList = this.parent
2414+
.select("svg")
2415+
.selectAll("g.tidytree-node-leaf circle, g.tidytree-node-internal circle")
2416+
._groups[0];
2417+
}
2418+
2419+
let nodeGUIDs = [];
2420+
for (const node of nodeList.values()) {
2421+
nodeGUIDs.push(node.__data__.data._guid);
2422+
}
2423+
2424+
return nodeGUIDs;
2425+
};
2426+
23422427
/**
23432428
* Searches the tree, returns Search Results
23442429
* @param {Function} test A function which takes a Branch and returns a Truthy

0 commit comments

Comments
 (0)