// origins = ilxtr:hasSomaLocatedIn // destinations = ilxtr:hasAxonPresynapticElementIn, ilxtr:hasAxonSensorySubcellularElementIn // via = ilxtr:hasAxonLeadingToSensorySubcellularElementIn, ilxtr:hasAxonLocatedIn import { findTaxonomyLabels } from "./flatmapQueries"; async function query(flatmapAPI, sql, params) { const url = `${flatmapAPI}knowledge/query/`; const query = { sql, params }; try { const response = await fetch(url, { method: 'POST', headers: { "Accept": "application/json; charset=utf-8", "Content-Type": "application/json" }, body: JSON.stringify(query) }); if (!response.ok) { throw new Error(`Cannot access ${url}`); } return await response.json(); } catch { return { values: [] }; } } async function fetchLabels(flatmapAPI, labelledTerms) { if (!labelledTerms.length) return []; const data = await query( flatmapAPI, `select entity, knowledge from knowledge where entity in (?${', ?'.repeat(labelledTerms.length - 1)}) order by source desc`, [...labelledTerms] ); return await data.values; } function filterOrigins(item) { const soma = item["node-phenotypes"]?.["ilxtr:hasSomaLocatedIn"]; return Array.isArray(item.connectivity) && item.connectivity.length > 0 && Array.isArray(soma) && soma.length > 0; } function filterDestinations(item) { const axonPresyn = item["node-phenotypes"]?.["ilxtr:hasAxonPresynapticElementIn"]; const axonSensory = item["node-phenotypes"]?.["ilxtr:hasAxonSensorySubcellularElementIn"]; const hasDest = (Array.isArray(axonPresyn) && axonPresyn.length > 0) || (Array.isArray(axonSensory) && axonSensory.length > 0); return Array.isArray(item.connectivity) && item.connectivity.length > 0 && hasDest; } function filterViaLocations(item) { if (!Array.isArray(item.connectivity) || item.connectivity.length === 0) return false; const origins = new Set( (item["node-phenotypes"]?.["ilxtr:hasSomaLocatedIn"] || []).map(arr => arr[0]) ); const destinations = new Set([ ...((item["node-phenotypes"]?.["ilxtr:hasAxonPresynapticElementIn"] || []).map(arr => arr[0])), ...((item["node-phenotypes"]?.["ilxtr:hasAxonSensorySubcellularElementIn"] || []).map(arr => arr[0])) ]); return item.connectivity.some(pair => { const [from, to] = pair; const fromId = from[0]; const toId = to[0]; return ( !origins.has(fromId) && !destinations.has(toId) ); }); } function getConnectivityItems(obj) { if (!Array.isArray(obj.connectivity)) return []; const items = new Set(); obj.connectivity.forEach((pair) => { if (Array.isArray(pair) && pair.length) { pair.forEach((endpoint) => { if (Array.isArray(endpoint) && typeof endpoint[0] === 'string') { const stringifyEndpoint = JSON.stringify(endpoint); items.add(stringifyEndpoint); } }); } }); return Array.from(items); } function getPhenotypeItems(obj, prop) { const arr = obj["node-phenotypes"]?.[prop]; if (!Array.isArray(arr)) return []; return arr; } async function transformResults(flatmapAPI, knowledgeSource, results) { const baseResults = Array.from( new Map(results.map(item => [JSON.stringify(item), item])).values() ); const terms = baseResults.flat(Infinity); const uniqueTerms = [...new Set(terms)]; const fetchResults = await fetchLabels(flatmapAPI, uniqueTerms); // const objectResults = fetchResults.map((item) => JSON.parse(item[1])); const objectResults = fetchResults.reduce((arr, item) => { const id = item[0]; const valObj = JSON.parse(item[1]); if (valObj.source === knowledgeSource) { arr.push({ id, label: valObj.label }); } return arr; }, []); const nodes = []; const formattedResults = baseResults.map((item) => { const itemPair = item.flat(); const labels = []; for (let i = 0; i < itemPair.length; i++) { const foundObj = objectResults.find((obj) => obj.id === itemPair[i]) if (foundObj) { labels.push(foundObj.label); if (i > 0) { nodes.push({ key: [itemPair[i], []], label: foundObj.label, }); } } } return { key: item, label: labels.join(', '), }; }); // unique results by combining formattedResults and nodes // but filter out duplicates based on the labels const uniqueResults = [...formattedResults, ...nodes].filter((result, index, self) => index === self.findIndex((r) => r.label === result.label) ); return uniqueResults; } async function extractOriginItems(flatmapAPI, knowledgeSource, knowledge) { const results = []; knowledge.forEach(obj => { if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return; const connectivityItems = new Set(getConnectivityItems(obj)); getPhenotypeItems(obj, "ilxtr:hasSomaLocatedIn").forEach((item) => { const stringifyItem = JSON.stringify(item); if (connectivityItems.has(stringifyItem)) results.push(item); }); }); return await transformResults(flatmapAPI, knowledgeSource, results); } async function extractDestinationItems(flatmapAPI, knowledgeSource, knowledge) { const results = []; knowledge.forEach(obj => { if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return; const connectivityItems = new Set(getConnectivityItems(obj)); [ ...getPhenotypeItems(obj, "ilxtr:hasAxonPresynapticElementIn"), ...getPhenotypeItems(obj, "ilxtr:hasAxonSensorySubcellularElementIn") ].forEach(item => { const stringifyItem = JSON.stringify(item); if (connectivityItems.has(stringifyItem)) results.push(item); }); }); return await transformResults(flatmapAPI, knowledgeSource, results); } async function extractViaItems(flatmapAPI, knowledgeSource, knowledge) { const results = []; knowledge.forEach(obj => { if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return; const connectivityItems = new Set(getConnectivityItems(obj)); [ ...getPhenotypeItems(obj, "ilxtr:hasAxonLeadingToSensorySubcellularElementIn"), ...getPhenotypeItems(obj, "ilxtr:hasAxonLocatedIn") ].forEach(item => { const stringifyItem = JSON.stringify(item); if (connectivityItems.has(stringifyItem)) results.push(item); }); }); return await transformResults(flatmapAPI, knowledgeSource, results); } function findPathsByOriginItem(knowledge, originItems) { return knowledge.filter(obj => { if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return false; const origins = getPhenotypeItems(obj, "ilxtr:hasSomaLocatedIn"); return origins.some(item => originItems.map(i => JSON.stringify(i)).includes(JSON.stringify(item))); }); } function findPathsByDestinationItem(knowledge, destinationItems) { return knowledge.filter(obj => { if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return false; const destinations = [ ...getPhenotypeItems(obj, "ilxtr:hasAxonPresynapticElementIn"), ...getPhenotypeItems(obj, "ilxtr:hasAxonSensorySubcellularElementIn") ]; return destinations.some(item => destinationItems.map(i => JSON.stringify(i)).includes(JSON.stringify(item))); }); } function findPathsByViaItem(knowledge, viaItems) { return knowledge.filter(obj => { if (!Array.isArray(obj.connectivity) || obj.connectivity.length === 0) return false; const vias = [ ...getPhenotypeItems(obj, "ilxtr:hasAxonLeadingToSensorySubcellularElementIn"), ...getPhenotypeItems(obj, "ilxtr:hasAxonLocatedIn") ]; return vias.some(item => viaItems.map(i => JSON.stringify(i)).includes(JSON.stringify(item))); }); } async function queryPathsByRouteFromKnowledge({ knowledge, origins, destinations, vias }) { let results = knowledge; if (origins.length) { results = findPathsByOriginItem(results, origins); } if (destinations.length) { results = findPathsByDestinationItem(results, destinations); } if (vias.length) { results = findPathsByViaItem(results, vias); } return results; } async function getFlatmapFilterOptions (flatmapAPI, mapImp, providedKnowledge, providedPathways) { let filterOptions = []; const connectionFilters = []; if (mapImp) { // get flatmap filters if (mapImp && typeof mapImp.featureFilterRanges === 'function') { const filterRanges = mapImp.featureFilterRanges(); for (const [key, value] of Object.entries(filterRanges)) { let main = { key: `flatmap.connectivity.${key}`, label: "", children: [] } let children = [] if (key === "kind") { main.label = "Pathways" for (const facet of value) { const pathway = providedPathways.find(path => path.type === facet) if (pathway) { children.push({ key: `${main.key}.${facet}`, label: pathway.label, colour: pathway.colour, colourStyle: 'line', dashed: pathway.dashed, }) } } } else if (key === "taxons") { main.label = "Studied in" const entityLabels = await findTaxonomyLabels(mapImp, mapImp.taxonIdentifiers) if (entityLabels.length) { for (const facet of value) { const taxon = entityLabels.find(p => p.taxon === facet) if (taxon) { children.push({ key: `${main.key}.${facet}`, // space added at the end of label to make sure the display name will not be updated // prevent sidebar searchfilter convertReadableLabel label: `${taxon.label} ` }) } } } } else if (key === "alert") { main.label = "Alert" for (const facet of ["with", "without"]) { children.push({ key: `${main.key}.${facet}`, label: `${facet} alerts` }) } } main.children = children.sort((a, b) => a.label.localeCompare(b.label)); if (main.label && main.children.length) { filterOptions.push(main) } } } const mapKnowledge = mapImp.pathways.paths; const flatmapKnowledge = providedKnowledge.reduce((arr, knowledge) => { const id = knowledge.id; if (id) { const mapKnowledgeObj = mapKnowledge[id]; if (mapKnowledgeObj && mapKnowledgeObj.connectivity && mapKnowledgeObj['node-phenotypes']) { const mapConnectivity = mapKnowledgeObj.connectivity; const mapNodePhenotypes = mapKnowledgeObj['node-phenotypes']; // take only map connectivity knowledge.connectivity = [...mapConnectivity]; for (let key in knowledge['node-phenotypes']) { if (mapNodePhenotypes[key]) { // take only map node-phenotypes knowledge['node-phenotypes'][key] = [...mapNodePhenotypes[key]]; } } // to avoid mutation arr.push(JSON.parse(JSON.stringify(knowledge))); } } return arr; }, []); const knowledgeSource = mapImp.knowledgeSource; const originItems = await extractOriginItems(flatmapAPI, knowledgeSource, flatmapKnowledge); const viaItems = await extractViaItems(flatmapAPI, knowledgeSource, flatmapKnowledge); const destinationItems = await extractDestinationItems(flatmapAPI, knowledgeSource, flatmapKnowledge); const transformItem = (facet, item) => { const key = JSON.stringify(item.key); return { key: `flatmap.connectivity.source.${facet}.${key}`, label: item.label || key }; } for (const facet of ["origin", "via", "destination", "all"]) { let childrenList = [] if (facet === 'origin') { childrenList = originItems.map((item) => transformItem(facet, item)); } else if (facet === 'via') { childrenList = viaItems.map((item) => transformItem(facet, item)); } else if (facet === 'destination') { childrenList = destinationItems.map((item) => transformItem(facet, item)); } else { // All is the combination of origin, via, destination const allList = [ ...originItems.map((item) => transformItem(facet, item)), ...viaItems.map((item) => transformItem(facet, item)), ...destinationItems.map((item) => transformItem(facet, item)) ]; // Generate unique list since the same feature can be in origin, via, and destination const seenKeys = new Set(); childrenList = allList.filter(item => { if (seenKeys.has(item.key)) return false; seenKeys.add(item.key); return true; }); } // Those without label but key should be below childrenList = childrenList.sort((a, b) => { const isAlpha = (str) => /^[a-zA-Z]/.test(str); const aAlpha = isAlpha(a.label); const bAlpha = isAlpha(b.label); if (aAlpha && !bAlpha) return -1; if (!aAlpha && bAlpha) return 1; return a.label.localeCompare(b.label); }); if (childrenList.length) { connectionFilters.push({ key: `flatmap.connectivity.source.${facet}`, label: facet, children: childrenList, }) } } if (connectionFilters.length) { filterOptions.push(...connectionFilters) } } return filterOptions; } export { filterOrigins, filterDestinations, filterViaLocations, extractOriginItems, extractDestinationItems, extractViaItems, findPathsByOriginItem, findPathsByDestinationItem, findPathsByViaItem, queryPathsByRouteFromKnowledge, fetchLabels, getFlatmapFilterOptions, }