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/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(); - }); };