diff --git a/taxonium_data_handling/exporting.js b/taxonium_data_handling/exporting.js index 206477de..17f304c7 100644 --- a/taxonium_data_handling/exporting.js +++ b/taxonium_data_handling/exporting.js @@ -1,5 +1,9 @@ // can be called by local or server backend -export const getNextstrainSubtreeJson = async (subtree_root_id, nodes) => { +export const getNextstrainSubtreeJson = async ( + subtree_root_id, + nodes, + config +) => { const subtree_root = nodes.find((node) => node.node_id === subtree_root_id); const childMap = {}; @@ -15,29 +19,71 @@ export const getNextstrainSubtreeJson = async (subtree_root_id, nodes) => { } } + let muts = subtree_root.mutations.map((m) => config.mutations[m]); + const nucMuts = muts.filter((m) => m.type === "nt"); + const aaMuts = muts.filter((m) => m.type === "aa"); + const treeJson = { name: subtree_root.name, node_id: subtree_root.node_id, - node_attrs: {}, + node_attrs: { div: 0 }, + branch_attrs: { + mutations: { + nuc: nucMuts.map( + (m) => `${m.previous_residue}${m.residue_pos}${m.new_residue}` + ), + }, + }, }; + const alreadyIn = []; + aaMuts.forEach((m) => { + if (alreadyIn.includes(m.gene)) { + treeJson.branch_attrs.mutations[m.gene].push( + `${m.previous_residue}${m.residue_pos}${m.new_residue}` + ); + } else { + treeJson.branch_attrs.mutations[m.gene] = [ + `${m.previous_residue}${m.residue_pos}${m.new_residue}`, + ]; + alreadyIn.push(m.gene); + } + }); + Object.keys(subtree_root) .filter((v) => v.startsWith("meta_")) .map((v) => ({ [v.slice(5)]: { value: subtree_root[v] } })) - .forEach((v) => Object.assign(treeJson.node_attrs, v)); + .forEach((v) => { + Object.assign(treeJson.node_attrs, v); + }); const stack = [treeJson]; + const metadataSet = new Set(); while (stack.length > 0) { const currNodeJson = stack.pop(); + const currNodeDiv = currNodeJson.node_attrs.div; const children = childMap[currNodeJson.node_id]; const childrenJson = []; if (children !== undefined) { for (const child_id of children) { const child_node = lookup[child_id]; + let muts = child_node.mutations.map((m) => config.mutations[m]); + const nucMuts = muts.filter((m) => m.type === "nt"); + const aaMuts = muts.filter((m) => m.type === "aa"); + console.log(nucMuts, aaMuts); + const nucMutsNoAmb = nucMuts.filter( + (m) => m.new_residue != "-" && m.previous_residue != "-" + ); + // TODO: Above discards ambiguities from distance calculation. + // Do we want to do this? In mpx e.g. there are nodes with + // many thousands of ambiguous mutations which throws off display const childJson = { name: child_node.name, node_id: child_node.node_id, - node_attrs: {}, + node_attrs: { + div: currNodeDiv + nucMutsNoAmb.length, + }, + branch_attrs: {}, }; // TODO add div key for genetic distance @@ -45,19 +91,33 @@ export const getNextstrainSubtreeJson = async (subtree_root_id, nodes) => { Object.keys(child_node) .filter((v) => v.startsWith("meta_")) .map((v) => ({ [v.slice(5)]: { value: child_node[v] } })) - .forEach((v) => Object.assign(childJson.node_attrs, v)); - + .forEach((v) => { + metadataSet.add(Object.keys(v)[0]); + Object.assign(childJson.node_attrs, v); + }); childrenJson.push(childJson); stack.push(childJson); } } - currNodeJson.children = childrenJson; + if (childrenJson.length > 0) { + currNodeJson.children = childrenJson; + } } let json = { meta: { description: "JSON exported from Taxonium.", panels: ["tree"], - title: "Taxonium JSON", + title: config.title, + description: "Source: " + config.source, + display_defaults: { + distance_measure: "div", + color_by: metadataSet[0], + }, + colorings: Array.from(metadataSet).map((v) => ({ + key: v, + title: v, + type: "categorical", + })), }, tree: treeJson, version: "v2", diff --git a/taxonium_web_client/src/Taxonium.jsx b/taxonium_web_client/src/Taxonium.jsx index 8b1e6772..01a742da 100644 --- a/taxonium_web_client/src/Taxonium.jsx +++ b/taxonium_web_client/src/Taxonium.jsx @@ -56,6 +56,9 @@ function Taxonium({ }, [config.colorMapping]); const colorHook = useColor(colorMapping); + //TODO: this is always true for now + config.enable_ns_download = true; + const xType = query.xType; const setxType = useCallback( (xType) => { diff --git a/taxonium_web_client/src/components/SearchPanel.jsx b/taxonium_web_client/src/components/SearchPanel.jsx index 5197ffee..985bb323 100644 --- a/taxonium_web_client/src/components/SearchPanel.jsx +++ b/taxonium_web_client/src/components/SearchPanel.jsx @@ -65,8 +65,7 @@ function SearchPanel({ const handleDownloadJson = () => { if (selectedDetails.nodeDetails) { const node_id = selectedDetails.nodeDetails.node_id; - console.log("json for node", selectedDetails.nodeDetails); - backend.getNextstrainJson(node_id); + backend.getNextstrainJson(node_id, config); } }; diff --git a/taxonium_web_client/src/hooks/useLocalBackend.js b/taxonium_web_client/src/hooks/useLocalBackend.js index 7b20fc7c..1cd19177 100644 --- a/taxonium_web_client/src/hooks/useLocalBackend.js +++ b/taxonium_web_client/src/hooks/useLocalBackend.js @@ -190,11 +190,12 @@ function useLocalBackend(uploaded_data, proto) { }; }, []); - const getNextstrainJson = useCallback((nodeId) => { + const getNextstrainJson = useCallback((nodeId, config) => { console.log("getNextstrainJson", nodeId); worker.postMessage({ type: "nextstrain", node_id: nodeId, + config: config, }); }, []); diff --git a/taxonium_web_client/src/webworkers/localBackendWorker.js b/taxonium_web_client/src/webworkers/localBackendWorker.js index 0c488594..cd7fd453 100644 --- a/taxonium_web_client/src/webworkers/localBackendWorker.js +++ b/taxonium_web_client/src/webworkers/localBackendWorker.js @@ -396,7 +396,8 @@ onmessage = async (event) => { if (data.type === "nextstrain") { const result = await getNextstrainSubtreeJson( data.node_id, - processedUploadedData.nodes + processedUploadedData.nodes, + data.config ); postMessage({ type: "nextstrain", data: result }); }