diff --git a/node_modules/.yarn-integrity b/node_modules/.yarn-integrity new file mode 100644 index 00000000..0c416750 --- /dev/null +++ b/node_modules/.yarn-integrity @@ -0,0 +1,13 @@ +{ + "systemParams": "darwin-arm64-108", + "modulesFolders": [], + "flags": [], + "linkedModules": [ + "my-project", + "taxonium-component" + ], + "topLevelPatterns": [], + "lockfileEntries": {}, + "files": [], + "artifacts": {} +} diff --git a/taxonium_component/src/hooks/useSearch.jsx b/taxonium_component/src/hooks/useSearch.jsx index f5b5aa6e..c00a52d4 100644 --- a/taxonium_component/src/hooks/useSearch.jsx +++ b/taxonium_component/src/hooks/useSearch.jsx @@ -37,7 +37,6 @@ const useSearch = ({ const searchesEnabled = query.enabled ? JSON.parse(query.enabled) : JSON.parse(default_query.enabled); - console.log("searchesEnabled", searchesEnabled); const setEnabled = (key, enabled) => { console.log("setEnabled", key, enabled); diff --git a/taxonium_component/src/stories/Taxonium.stories.jsx b/taxonium_component/src/stories/Taxonium.stories.jsx index 3f422d7f..79a84311 100644 --- a/taxonium_component/src/stories/Taxonium.stories.jsx +++ b/taxonium_component/src/stories/Taxonium.stories.jsx @@ -113,3 +113,17 @@ export const LocalDataWithMetadataNew = { layout: "padded", }, }; + +export const NexusTree = { + args: { + sourceData: { + status: "url_supplied", + filename: "https://cov2tree.nyc3.cdn.digitaloceanspaces.com/nexus.tree", + filetype: "nexus", + }, + }, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "padded", + }, +}; diff --git a/taxonium_component/src/utils/nexusToNewick.js b/taxonium_component/src/utils/nexusToNewick.js index 74219861..895af99c 100644 --- a/taxonium_component/src/utils/nexusToNewick.js +++ b/taxonium_component/src/utils/nexusToNewick.js @@ -1,4 +1,3 @@ -// get nexusString from tree.nexus function nexusToNewick(nexusString) { // get Translate section if present const translateBlock = nexusString.match(/Translate(.*?);/gims); @@ -27,7 +26,25 @@ function nexusToNewick(nexusString) { // get the Newick string from the tree block const newickString = treeBlock[0].match(/\((.*?)\).+;/gims)[0]; - //remove comments, which are indicated by [...] + let nodeProperties = {}; + + // extract properties, which are indicated by [&key=value] or [&key={value1,value2,...}] + newickString.replace( + /\[&?(.*?)\]/gims, + (match, contents, offset, inputString) => { + let nodeId = inputString.slice(0, offset).match(/[^,\(\):]+$/g)[0]; + // use a regular expression to split on commas not inside curly brackets + let properties = contents.split(/,(?![^{]*})/g); + let propertyDict = {}; + for (let prop of properties) { + let [key, value] = prop.split("="); + propertyDict["meta_" + key] = value; + } + nodeProperties[nodeId] = propertyDict; + } + ); + + // remove comments, which are indicated by [...] const newick = newickString.replace(/\[(.*?)\]/gims, ""); @@ -35,12 +52,11 @@ function nexusToNewick(nexusString) { const translatedNewickString = newick.replace( /([^:\,\(\)]+)/gims, (match) => { - //console.log(translations[match]) return translations[match] || match; } ); - return translatedNewickString; + return { newick: translatedNewickString, nodeProperties }; } export default nexusToNewick; diff --git a/taxonium_component/src/utils/processNewick.js b/taxonium_component/src/utils/processNewick.js index 531101ba..614aee80 100644 --- a/taxonium_component/src/utils/processNewick.js +++ b/taxonium_component/src/utils/processNewick.js @@ -117,12 +117,15 @@ async function cleanup(tree) { export async function processNewick(data, sendStatusMessage) { let the_data; + let extra_metadata; the_data = await fetch_or_extract(data, sendStatusMessage, "tree"); console.log("data.filetype", data.filetype); if (data.filetype == "nexus") { - the_data = nexusToNewick(the_data); + const result = nexusToNewick(the_data); + the_data = result.newick; + extra_metadata = result.nodeProperties; } sendStatusMessage({ @@ -205,6 +208,7 @@ export async function processNewick(data, sendStatusMessage) { rootMutations: [], rootId: 0, overwrite_config: { num_tips: total_tips, from_newick: true }, + extra_metadata, }; return output; @@ -276,29 +280,55 @@ export async function processMetadataFile(data, sendStatusMessage) { export async function processNewickAndMetadata(data, sendStatusMessage) { const treePromise = processNewick(data, sendStatusMessage); - + let tree, metadata_double; + let metadata = new Map(); + let headers = []; const metadataInput = data.metadata; if (!metadataInput) { - return await treePromise; + tree = await treePromise; + } else { + // Wait for both promises to resolve + [tree, metadata_double] = await Promise.all([ + treePromise, + processMetadataFile(metadataInput, sendStatusMessage), + ]); + [metadata, headers] = metadata_double; } - // Wait for both promises to resolve - const [tree, metadata_double] = await Promise.all([ - treePromise, - processMetadataFile(metadataInput, sendStatusMessage), - ]); - const [metadata, headers] = metadata_double; + const blanks = Object.fromEntries( headers.slice(1).map((x) => ["meta_" + x, ""]) ); + + if (tree.extra_metadata) { + // loop over the extra metadata dict to find all the (sub)keys + const all_extra_keys = new Set(); + Object.values(tree.extra_metadata).forEach((node_extra) => { + Object.keys(node_extra).forEach((key) => { + all_extra_keys.add(key); + }); + }); + // add any misssing keys to blanks + all_extra_keys.forEach((key) => { + if (!blanks[key]) { + blanks[key] = ""; + } + }); + } + sendStatusMessage({ message: "Assigning metadata to nodes", }); tree.nodes.forEach((node) => { const this_metadata = metadata.get(node.name); + Object.assign(node, blanks); if (this_metadata) { Object.assign(node, this_metadata); - } else { - Object.assign(node, blanks); + } + if (tree.extra_metadata) { + const node_extra = tree.extra_metadata[node.name]; + if (node_extra) { + Object.assign(node, node_extra); + } } });