diff --git a/CHANGELOG.md b/CHANGELOG.md index c720c0b0f..ace50a784 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/src/components/tree/reactD3Interface/change.js b/src/components/tree/reactD3Interface/change.js index 9e759e5af..07bff8b1b 100644 --- a/src/components/tree/reactD3Interface/change.js +++ b/src/components/tree/reactD3Interface/change.js @@ -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 = {}; @@ -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 */ diff --git a/src/components/tree/reactD3Interface/initialRender.js b/src/components/tree/reactD3Interface/initialRender.js index 880fd87c9..b85dd9917 100644 --- a/src/components/tree/reactD3Interface/initialRender.js +++ b/src/components/tree/reactD3Interface/initialRender.js @@ -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"; @@ -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), @@ -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 diff --git a/src/reducers/controls.ts b/src/reducers/controls.ts index 1d4a5859b..e6b3919da 100644 --- a/src/reducers/controls.ts +++ b/src/reducers/controls.ts @@ -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", diff --git a/src/util/colorHelpers.js b/src/util/colorHelpers.js index 389e143cf..d202c2bdd 100644 --- a/src/util/colorHelpers.js +++ b/src/util/colorHelpers.js @@ -68,11 +68,14 @@ 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 @@ -80,23 +83,39 @@ export const branchOpacityFunction = scalePow() // 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(); - }); };