Skip to content

Commit

Permalink
Merge pull request #1655 from nextstrain/continous-scale-fixes
Browse files Browse the repository at this point in the history
Type cast continuous values in tree
  • Loading branch information
jameshadfield authored Jun 6, 2023
2 parents 0438286 + eca540e commit 4b06389
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/actions/recomputeReduxState.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getDefaultMeasurementsState } from "../reducers/measurements";
import { countTraitsAcrossTree, calcTotalTipsInTree } from "../util/treeCountingHelpers";
import { calcEntropyInView } from "../util/entropy";
import { treeJsonToState } from "../util/treeJsonProcessing";
import { castIncorrectTypes } from "../util/castJsonTypes";
import { entropyCreateState } from "../util/entropyCreateStateFromJsons";
import { determineColorByGenotypeMutType, calcNodeColor } from "../util/colorHelpers";
import { calcColorScale, createVisibleLegendValues } from "../util/colorScale";
Expand Down Expand Up @@ -771,11 +772,13 @@ export const createStateFromQueryOrJSONs = ({
measurements = getDefaultMeasurementsState();
/* new tree state(s) */
tree = treeJsonToState(json.tree);
castIncorrectTypes(metadata, tree);
tree.debug = "LEFT";
tree.name = mainTreeName;
metadata.mainTreeNumTips = calcTotalTipsInTree(tree.nodes);
if (secondTreeDataset) {
treeToo = treeJsonToState(secondTreeDataset.tree);
castIncorrectTypes(metadata, treeToo);
treeToo.debug = "RIGHT";
treeToo.name = secondTreeName;
/* TODO: calc & display num tips in 2nd tree */
Expand Down
56 changes: 56 additions & 0 deletions src/util/castJsonTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Values in the JSON should be appropriately typed however this may not always be the case.
* For instance, certain values of a continuous trait may be strings, and we should cast
* these to numbers. See https://github.com/nextstrain/auspice/issues/1626 for more discussion
* of this particular case. Feel free to add more checks / casts to this function!
* @param {Object} metadata
* @param {Object} tree
* @returns {undefined} Any type casting is in-place
*/
export const castIncorrectTypes = (metadata, tree) => {
try {
const continuousKeys = new Set();
Object.entries(metadata.colorings || {}).forEach(([key, details]) => {
if (details.type==="continuous") {
continuousKeys.add(key);
}
});
tree.nodes.forEach((node) => {
for (const key of continuousKeys) {
const attr = node?.node_attrs?.[key];
if (!attr) continue;
continuousAttrValueToNumber(attr);
}
});
} catch (err) {
// type casting shouldn't be required (the JSON should be correctly typed)
// so any errors shouldn't prevent Auspice loading
console.error(err);
}
};

/**
* Cast `attr.value` to either a Number or undefined.
* Note that "Infinity" (string) is changed to undefined, but `Infinity` (Number)
* is left alone as the latter is not possible to encode in JSON.
* @param {Record<string, any>} attr
*/
export function continuousAttrValueToNumber(attr) {
switch (typeof attr.value) {
case "number":
break;
case "string":
const value = attr.value.trim();
if (value === "" || isNaN(value) || value ==="Infinity" || value ==="-Infinity") {
// Note: Number("")=0
// undefined values are handled appropriately (e.g. scatterplots, tooltips etc)
attr.value = undefined;
} else {
attr.value = Number(value);
}
break;
default:
// any other types (Boolean, Null ('object')) are not valid for a continuous scale
attr.value = undefined;
}
}
20 changes: 20 additions & 0 deletions test/jsonParsing.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { continuousAttrValueToNumber } from "../src/util/castJsonTypes";

/**
* function used to cast node values of continuous traits to numbers
*/
test("Continuous scale values are cast to Numbers or are undefined", () => {
const numbers = ["123", "1e5", "1", "0", "-1", 0, 1, -1];
const notNumbers = ["", " ", " Infinity", "infinity", "Infinity", null, "abc", "", true, false, "true", undefined];
numbers.forEach((n) => {
const attr = {value: n};
continuousAttrValueToNumber(attr); // modifies in place
expect(typeof attr.value).toStrictEqual('number');
expect(Number.isFinite(attr.value)).toBe(true);
});
notNumbers.forEach((n) => {
const attr = {value: n};
continuousAttrValueToNumber(attr); // modifies in place
expect(attr.value).toBeUndefined();
});
});

0 comments on commit 4b06389

Please sign in to comment.