-
Notifications
You must be signed in to change notification settings - Fork 163
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1655 from nextstrain/continous-scale-fixes
Type cast continuous values in tree
- Loading branch information
Showing
3 changed files
with
79 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); |