Skip to content

Commit

Permalink
Merge pull request #1796 from nextstrain/james/uncertain-tip-attrs
Browse files Browse the repository at this point in the history
Convey uncertainty via tip colors
  • Loading branch information
jameshadfield authored Jun 30, 2024
2 parents 2c59883 + c2ffa90 commit 3e818f0
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changelog

* We now use the reported confidence / entropy values to change the saturation of tips (circles) on the tree, which matches the behaviour seen for branches. If there is no (or very little) uncertainty in these nodes then the tips will appear the same as seen in previous versions of Auspice. ([#1796](https://github.com/nextstrain/auspice/pull/1796))
* We no longer show the "second tree" sidebar dropdown when there are no available options. The possible options are defined by [the charon/getAvailable API](https://docs.nextstrain.org/projects/auspice/en/stable/server/api.html) response and as such vary depending on the server in use. ([#1795](https://github.com/nextstrain/auspice/pull/1795))


Expand Down
8 changes: 4 additions & 4 deletions src/components/tree/reactD3Interface/change.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { calcBranchStrokeCols, getBrighterColor } from "../../../util/colorHelpers";
import { calculateStrokeColors, getBrighterColor } from "../../../util/colorHelpers";

export const changePhyloTreeViaPropsComparison = (mainTree, phylotree, oldProps, newProps) => {
const args = {};
Expand All @@ -16,9 +16,9 @@ export const changePhyloTreeViaPropsComparison = (mainTree, phylotree, oldProps,
(oldTreeRedux.nodeColorsVersion !== newTreeRedux.nodeColorsVersion ||
newProps.colorByConfidence !== oldProps.colorByConfidence)) {
args.changeColorBy = true;
args.branchStroke = calcBranchStrokeCols(newTreeRedux, newProps.colorByConfidence, newProps.colorBy);
args.tipStroke = newTreeRedux.nodeColors;
args.fill = newTreeRedux.nodeColors.map(getBrighterColor);
args.branchStroke = calculateStrokeColors(newTreeRedux, true, newProps.colorByConfidence, newProps.colorBy);
args.tipStroke = calculateStrokeColors(newTreeRedux, false, newProps.colorByConfidence, newProps.colorBy);
args.fill = args.tipStroke.map(getBrighterColor);
}

/* visibility */
Expand Down
9 changes: 5 additions & 4 deletions src/components/tree/reactD3Interface/initialRender.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { select } from "d3-selection";
import 'd3-transition';
import { calcBranchStrokeCols, getBrighterColor } from "../../../util/colorHelpers";
import { calculateStrokeColors, getBrighterColor } from "../../../util/colorHelpers";
import * as callbacks from "./callbacks";
import { makeTipLabelFunc } from "../phyloTree/labels";

Expand All @@ -16,6 +16,7 @@ export const renderTree = (that, main, phylotree, props) => {
if (Object.prototype.hasOwnProperty.call(props.scatterVariables, "showBranches") && props.scatterVariables.showBranches===false) {
renderBranchLabels=false;
}
const tipStrokeColors = calculateStrokeColors(treeState, false, props.colorByConfidence, props.colorBy);
/* simply the call to phylotree.render */
phylotree.render(
select(ref),
Expand Down Expand Up @@ -43,9 +44,9 @@ export const renderTree = (that, main, phylotree, props) => {
treeState.visibility,
props.temporalConfidence.on, /* drawConfidence? */
treeState.vaccines,
calcBranchStrokeCols(treeState, props.colorByConfidence, props.colorBy),
treeState.nodeColors,
treeState.nodeColors.map(getBrighterColor),
calculateStrokeColors(treeState, true, props.colorByConfidence, props.colorBy),
tipStrokeColors,
tipStrokeColors.map(getBrighterColor), // tip fill colors
treeState.tipRadii, /* might be null */
[props.dateMinNumeric, props.dateMaxNumeric],
props.scatterVariables
Expand Down
2 changes: 1 addition & 1 deletion src/reducers/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const getDefaultControlsState = () => {
absoluteDateMax: dateMax,
absoluteDateMaxNumeric: dateMaxNumeric,
colorBy: defaults.colorBy,
colorByConfidence: { display: false, on: false },
colorByConfidence: false,
colorScale: undefined,
explodeAttr: undefined,
selectedBranchLabel: "none",
Expand Down
47 changes: 33 additions & 14 deletions src/util/colorHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,35 +68,54 @@ export const calcNodeColor = (tree, colorScale) => {
// scale entropy such that higher entropy maps to a grayer less-certain branch
const branchInterpolateColour = "#BBB";
const branchOpacityConstant = 0.6;
export const branchOpacityFunction = scalePow()
const branchOpacityFunction = scalePow()
.exponent([0.6])
.domain([0, 2.0])
.range([0.4, 1])
.domain([0, 2.0]) // entropy values close to 0 -> ~100% confidence, close to 2 -> very little confidence
.range([0.4, 1]) // 0 -> return original node colour, 1 -> return branchInterpolateColour
.clamp(true);
const tipOpacityFunction = branchOpacityFunction
.copy()
.range([0, 0.9]); // if entropy close to 0 return the original node color


// entropy calculation precomputed in augur
// export const calcEntropyOfValues = (vals) =>
// vals.map((v) => v * Math.log(v + 1E-10)).reduce((a, b) => a + b, 0) * -1 / Math.log(vals.length);

/**
* calculate array of HEXs to actually be displayed.
* (colorBy) confidences manifest as opacity ramps
* Calculate an array of stroke colors to render for a branch or tip node. These are "grey-er" versions
* of the underlying `tree.nodeColours`. The degree of grey-ness is obtained via interpolation
* between the node color and `branchOpacityConstant`. The interpolation parameter varies
* depending on the confidence we have in the trait (the entropy), with more confidence resulting
* in more saturated colours. For branches we always make them slightly greyer (even in the absence
* of uncertainty) for purely aesthetic reasons.
* @param {obj} tree phyloTree object
* @param {bool} branch will this color be used for the branch or the tip?
* @param {bool} confidence enabled?
* @return {array} array of hex's. 1-1 with nodes.
*/
export const calcBranchStrokeCols = (tree, confidence, colorBy) => {
export const calculateStrokeColors = (tree, branch, confidence, colorBy) => {
if (confidence === true) {
return tree.nodeColors.map((col, idx) => {
const entropy = getTraitFromNode(tree.nodes[idx], colorBy, {entropy: true});
const opacity = entropy ? branchOpacityFunction(entropy) : branchOpacityConstant;
return rgb(interpolateRgb(col, branchInterpolateColour)(opacity)).toString();
});
return tree.nodeColors.map(branch ? _confidenceBranchColor : _confidenceTipColor)
}
return branch ? tree.nodeColors.map(_defaultBranchColor) : tree.nodeColors;

function _confidenceBranchColor(col, idx) {
const entropy = getTraitFromNode(tree.nodes[idx], colorBy, {entropy: true});
if (!entropy) return _defaultBranchColor(col);
return rgb(interpolateRgb(col, branchInterpolateColour)(branchOpacityFunction(entropy))).toString();
}

function _confidenceTipColor(col, idx) {
if (tree.nodes[idx].hasChildren) return undefined; // skip computation for internal nodes
const entropy = getTraitFromNode(tree.nodes[idx], colorBy, {entropy: true});
if (!entropy) return col;
return rgb(interpolateRgb(col, branchInterpolateColour)(tipOpacityFunction(entropy))).toString();
}

function _defaultBranchColor(col) {
return rgb(interpolateRgb(col, branchInterpolateColour)(branchOpacityConstant)).toString()
}
return tree.nodeColors.map((col) => {
return rgb(interpolateRgb(col, branchInterpolateColour)(branchOpacityConstant)).toString();
});
};


Expand Down

0 comments on commit 3e818f0

Please sign in to comment.