Skip to content

Commit

Permalink
[color-by confidence] confidence for tip colors
Browse files Browse the repository at this point in the history
The previous code conveyed uncertainty in node attrs for _branches_ by
making them appear grey-er, but we never implemented this for _tips_;
most likely because we never had a dataset with such data when this was
built.

Here we use the same approach for tips as for branches, but with a
slightly different parameterisation of the interpolation. The mapping of
the entropy value into `[0,1]` (`tipOpacityFunction`) was chosen so that
tips with no (or very little) uncertainty look unchanged from previous
Auspice versions, and uncertainty makes them appear more similar to the
branch colour (for an equivalent uncertainty).
  • Loading branch information
jameshadfield committed Jun 30, 2024
1 parent 95172c3 commit c2ffa90
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 22 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
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 c2ffa90

Please sign in to comment.