From 2504ac73441f1f749c3b1ca7e775ccedd2d61cde Mon Sep 17 00:00:00 2001 From: James Hadfield Date: Thu, 23 Mar 2023 12:27:16 +1300 Subject: [PATCH] Type cast continuous values in tree Conceptually these values only make sense if they are numeric. Previously some implicit casting was happening, but this resulted in bugs such as negative values not being displayed in the scatterplot. Note one slight side-effect which may be considered a regression: previously string values which were not numbers (e.g. "abc" but not "1.0") would show up in the hover/click info-boxes. Now such values will be changed to `undefined` and therefore not shown in those info-boxes. Timing of the changes introduced here (AppleM1, Firefox 111): * nextclade/sars-cov-2/21L 7-9ms * ncov/gisaid/global/6m 4-10ms * zika 2ms Closes #1626 --- src/actions/recomputeReduxState.js | 3 +++ src/util/castJsonTypes.js | 37 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/util/castJsonTypes.js diff --git a/src/actions/recomputeReduxState.js b/src/actions/recomputeReduxState.js index cdda54227..7accd1dd1 100644 --- a/src/actions/recomputeReduxState.js +++ b/src/actions/recomputeReduxState.js @@ -10,6 +10,7 @@ import { getDefaultFrequenciesState } from "../reducers/frequencies"; 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"; @@ -768,11 +769,13 @@ export const createStateFromQueryOrJSONs = ({ frequencies = getDefaultFrequenciesState(); /* 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 */ diff --git a/src/util/castJsonTypes.js b/src/util/castJsonTypes.js new file mode 100644 index 000000000..a792bed5e --- /dev/null +++ b/src/util/castJsonTypes.js @@ -0,0 +1,37 @@ +/** + * 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) => { + Object.entries(node.node_attrs || {}).forEach(([key, details]) => { + if (continuousKeys.has(key)) { + if ((typeof details.value) !== "number") { + if (details.value==="" || isNaN(details.value)) { // Note: Number("")=0 + // undefined values are handled appropriately (e.g. scatterplots, tooltips etc) + details.value = undefined; + } else { + details.value = Number(details.value); + } + } + } + }); + }); + } 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); + } +};