auspice
Version:
Web app for visualizing pathogen evolution
90 lines (78 loc) • 3.18 kB
text/typescript
import { tipRadius, tipRadiusOnLegendMatch } from "./globals";
import { getTipColorAttribute, numDate } from "./colorHelpers";
import { getTraitFromNode } from "./treeMiscHelpers";
import { ColorScale } from "../reducers/controls";
import { ReduxNode, TreeState } from "../reducers/tree/types";
/**
* equates a single tip and a legend element
* exact match is required for categorical quantities such as genotypes, regions
* continuous variables need to fall into the interval (lower_bound, upper_bound]
* except for the first (smallest) legend value where we also match value=lower_bound
*/
export const determineLegendMatch = (
/** e.g. "USA" or 2021 */
selectedLegendItem: string | number,
/** node (tip) in question */
node: ReduxNode,
/** used to get the value of the attribute being used for colouring */
colorScale: ColorScale
): boolean => {
let nodeAttr = getTipColorAttribute(node, colorScale);
if (colorScale.scaleType === 'temporal') {
nodeAttr = numDate(nodeAttr);
}
if (colorScale.continuous) {
if (selectedLegendItem === colorScale.legendValues[0] && nodeAttr===colorScale.legendBounds[selectedLegendItem][0]) {
return true;
}
return (nodeAttr <= colorScale.legendBounds[selectedLegendItem][1]) &&
(nodeAttr > colorScale.legendBounds[selectedLegendItem][0]);
}
return nodeAttr === selectedLegendItem;
};
/**
* Does the `node`s trait for the given `geoResolution` match the `geoValueToMatch`?
*/
const determineLocationMatch = (
/** node (tip) in question */
node: ReduxNode,
/** Geographic resolution (e.g. "division", "country", "region") */
geoResolution: string,
/** Value to match (e.g. "New Zealand", "New York") */
geoValueToMatch: string
): boolean => {
return geoValueToMatch === getTraitFromNode(node, geoResolution);
};
/**
* produces the array of tip radii - if nothing's selected this is the hardcoded tipRadius
* if there's a selectedLegendItem, then values will be small (like normal) or big (for those tips selected)
* @returns null (if data not ready) or array of tip radii
*/
export const calcTipRadii = ({
tipSelectedIdx = false,
selectedLegendItem = false,
geoFilter = [],
colorScale,
tree
}: {
/** idx of a single tip to show with increased tipRadius */
tipSelectedIdx?: number | false
/** value of the selected tip attribute (numeric or string) */
selectedLegendItem?: number | string | false
geoFilter?: [string, string] | []
colorScale: ColorScale
tree: TreeState
}): number[] | null => {
if (selectedLegendItem !== false && tree && tree.nodes) {
return tree.nodes.map((d) => determineLegendMatch(selectedLegendItem, d, colorScale) ? tipRadiusOnLegendMatch : tipRadius);
} else if (geoFilter.length===2 && tree && tree.nodes) {
return tree.nodes.map((d) => determineLocationMatch(d, geoFilter[0], geoFilter[1]) ? tipRadiusOnLegendMatch : tipRadius);
} else if (tipSelectedIdx) {
const radii = tree.nodes.map(() => tipRadius);
radii[tipSelectedIdx] = tipRadiusOnLegendMatch + 3;
return radii;
} else if (tree && tree.nodes) {
return tree.nodes.map(() => tipRadius);
}
return null; // fallthrough
};