diff --git a/src/components/Connections.tsx b/src/components/Connections.tsx index 8c4afd1..1fa1690 100644 --- a/src/components/Connections.tsx +++ b/src/components/Connections.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState } from "react"; import { Box, Chip, TextField, Typography } from "@mui/material"; import { ArrowRightIcon } from "./icons"; import { vars } from "../theme/variables"; -import { HierarchicalItem, SubConnections, PhenotypeDetail, SummaryType, KsMapType } from "./common/Types"; +import { HierarchicalItem, SubConnections, SummaryType, KsMapType, PhenotypeType } from "./common/Types"; import { useDataContext } from "../context/DataContext.ts"; import { calculateSecondaryConnections, @@ -58,17 +58,20 @@ function Connections() { const viasStatement = convertViaToString(Object.values(viasConnection)) const totalConnectionCount = Object.keys(selectedConnectionSummary?.connections || {} as KsMapType).length; const phenotypeNamesArray = useMemo(() => getAllPhenotypes(selectedConnectionSummary?.connections || {} as KsMapType), [selectedConnectionSummary]); - const [phenotypes, setPhenotypes] = useState([]); + const [phenotypes, setPhenotypes] = useState({}); useEffect(() => { if (selectedConnectionSummary && phenotypeNamesArray && phenotypeNamesArray.length > 0) { const phenotypeColors: string[] = generatePhenotypeColors(phenotypeNamesArray.length) - setPhenotypes(phenotypeNamesArray.map((phenotype, index) => ({ - label: phenotype, - color: phenotypeColors[index], - ksId: '' - }))) + const phenotypes: PhenotypeType = {}; + phenotypeNamesArray.forEach((phenotype, index) => { + phenotypes[phenotype] = { + label: phenotype, + color: phenotypeColors[index], + } + }) + setPhenotypes(phenotypes); } }, [phenotypeNamesArray, selectedConnectionSummary]) @@ -76,16 +79,13 @@ function Connections() { useEffect(() => { - if (selectedConnectionSummary && phenotypes) { + if (selectedConnectionSummary && phenotypes && selectedConnectionSummary.hierarchy && hierarchicalNodes) { const destinations = Array.from(selectedConnectionSummary.endOrgan?.children?.values()).reduce((acc, organ, index) => { acc[organ.id] = { ...organ, children: new Map(), order: index }; return acc; }, {} as Record); - const connections = calculateSecondaryConnections( - hierarchicalNodes, destinations, knowledgeStatements, summaryFilters, phenotypes, - selectedConnectionSummary.hierarchy - ); + const connections = calculateSecondaryConnections(hierarchicalNodes, destinations, knowledgeStatements, summaryFilters, selectedConnectionSummary.hierarchy) setConnectionsMap(connections); } }, [hierarchicalNodes, selectedConnectionSummary, summaryFilters, knowledgeStatements, phenotypes]); @@ -193,6 +193,7 @@ function Connections() { secondaryHeatmapData={heatmapData} xAxisLabel={'Project to'} yAxisLabel={'Somas in'} + phenotypes={phenotypes} /> diff --git a/src/components/SummaryFiltersDropdown.tsx b/src/components/SummaryFiltersDropdown.tsx index dd5e3d3..323285a 100644 --- a/src/components/SummaryFiltersDropdown.tsx +++ b/src/components/SummaryFiltersDropdown.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; import { SummaryFilters, useDataContext } from "../context/DataContext"; import CustomFilterDropdown from "./common/CustomFilterDropdown"; -import { Option, PhenotypeDetail } from "./common/Types"; +import { Option, PhenotypeType } from "./common/Types"; import { Box } from "@mui/material"; import { searchNerveFilter, searchPhenotypeFilter } from "../services/searchService"; import { OTHER_PHENOTYPE_LABEL } from "../settings"; @@ -27,7 +27,7 @@ const filterConfig: FilterConfig[] = [ const SummaryFiltersDropdown = ({ nerves, phenotypes }: { nerves: { [key: string]: string }, - phenotypes: PhenotypeDetail[] + phenotypes: PhenotypeType }) => { const { summaryFilters, setSummaryFilters } = useDataContext(); @@ -39,9 +39,9 @@ const SummaryFiltersDropdown = ({ nerves, phenotypes }: { content: [] })); } - const convertPhenotypesToOptions = (phenotypes: PhenotypeDetail[]): Option[] => { + const convertPhenotypesToOptions = (phenotypes: PhenotypeType): Option[] => { // filter the phenotype where label is other - return phenotypes.map(phenotype => ({ + return Object.values(phenotypes).map(phenotype => ({ id: phenotype.label, label: phenotype.label, group: 'Phenotype', diff --git a/src/components/common/Heatmap.tsx b/src/components/common/Heatmap.tsx index a067221..5b2517b 100644 --- a/src/components/common/Heatmap.tsx +++ b/src/components/common/Heatmap.tsx @@ -4,9 +4,10 @@ import { vars } from "../../theme/variables"; import CollapsibleList from "./CollapsibleList"; import HeatMap from "react-heatmap-grid"; import HeatmapTooltip from "./HeatmapTooltip"; -import { HierarchicalItem, SubConnections } from "./Types.ts"; +import { HierarchicalItem, PhenotypeType, SubConnections } from "./Types.ts"; import { getNormalizedValueForMinMax } from "../../services/summaryHeatmapService.ts"; import { generateYLabelsAndIds, getPhenotypeColors } from "../../services/heatmapService.ts"; +import { OTHER_PHENOTYPE_LABEL } from "../../settings.ts"; const { gray50, primaryPurple500, gray100A, gray500 } = vars; @@ -21,6 +22,7 @@ interface HeatmapGridProps { selectedCell?: { x: number, y: number } | null; heatmapData?: number[][]; secondaryHeatmapData?: SubConnections[][]; + phenotypes?: PhenotypeType; } const prepareSecondaryHeatmapData = (data?: SubConnections[][]): number[][] => { @@ -32,7 +34,7 @@ const prepareSecondaryHeatmapData = (data?: SubConnections[][]): number[][] => { const HeatmapGrid: FC = ({ xAxis, yAxis, setYAxis, xAxisLabel, yAxisLabel, - onCellClick, selectedCell, heatmapData, secondaryHeatmapData + onCellClick, selectedCell, heatmapData, secondaryHeatmapData, phenotypes }) => { const secondary = secondaryHeatmapData ? true : false; @@ -58,7 +60,6 @@ const HeatmapGrid: FC = ({ const yAxisData = generateYLabelsAndIds(yAxis); - const handleCellClick = (x: number, y: number) => { const ids = yAxisData.ids if (onCellClick) { @@ -71,8 +72,20 @@ const HeatmapGrid: FC = ({ _x: number, _y: number ) => { - if (secondary && secondaryHeatmapData && secondaryHeatmapData[_y] && secondaryHeatmapData[_y][_x]) { - const phenotypeColors = secondaryHeatmapData[_y][_x]?.colors; + if (phenotypes && secondary && secondaryHeatmapData && secondaryHeatmapData[_y] && secondaryHeatmapData[_y][_x]) { + const matrixCellPhenotypes = secondaryHeatmapData[_y][_x]?.phenotypes; + + const phenotypeColorsSet = new Set(); + matrixCellPhenotypes.forEach(phenotype => { + const phnColor = phenotypes[phenotype]?.color + if (phnColor) { + phenotypeColorsSet.add(phnColor); + } else { + phenotypeColorsSet.add(phenotypes[OTHER_PHENOTYPE_LABEL].color); + } + }); + + const phenotypeColors = Array.from(phenotypeColorsSet); const phenotypeColor = getPhenotypeColors(normalizedValue, phenotypeColors); return phenotypeColor ? phenotypeColor : `rgba(131, 0, 191, ${normalizedValue})`; diff --git a/src/components/common/Types.ts b/src/components/common/Types.ts index 7638b99..8001ed7 100644 --- a/src/components/common/Types.ts +++ b/src/components/common/Types.ts @@ -14,7 +14,7 @@ export type Option = { export type LabelIdPair = { labels: string[], ids: string[] }; export type KsMapType = Record; -export type SubConnections = { colors: string[], ksIds: Set }; +export type SubConnections = { phenotypes: string[], ksIds: Set }; export enum SummaryType { Summary = 'summary', DetailedSummary = 'detailedSummary', @@ -37,5 +37,8 @@ export interface HierarchicalItem { export type PhenotypeDetail = { label: string; color: string; - ksId: string; }; + +export type PhenotypeType = { + [key: string]: PhenotypeDetail; +} \ No newline at end of file diff --git a/src/components/connections/PhenotypeLegend.tsx b/src/components/connections/PhenotypeLegend.tsx index 941e4ce..45e7b3b 100644 --- a/src/components/connections/PhenotypeLegend.tsx +++ b/src/components/connections/PhenotypeLegend.tsx @@ -1,13 +1,14 @@ import { Box, Typography } from "@mui/material" -import { PhenotypeDetail } from "../common/Types" +import { PhenotypeDetail, PhenotypeType } from "../common/Types" import { vars } from "../../theme/variables"; const { gray100 } = vars; const PhenotypeLegend = ( - { phenotypes }: { phenotypes: PhenotypeDetail[] } + { phenotypes }: { phenotypes: PhenotypeType } ) => { + const phenotypesLegends = Object.values(phenotypes); return ( - {phenotypes?.map((phenotype: PhenotypeDetail) => ( + {phenotypesLegends?.map((phenotype: PhenotypeDetail) => ( , endorgans: Record, allKnowledgeStatements: Record, summaryFilters: SummaryFilters, - phenotypes: PhenotypeDetail[], hierarchyNode: HierarchicalNode + hierarchyNode: HierarchicalNode ): Map { // Apply filters to organs and knowledge statements @@ -147,12 +146,13 @@ export function calculateSecondaryConnections( } const node = hierarchicalNodes[nodeId]; - const result: SubConnections[] = Object.values(endorgans).map(() => ({ colors: [], ksIds: new Set() })); + const result: SubConnections[] = Object.values(endorgans).map(() => ({ phenotypes: [], ksIds: new Set() })); + if (node.children && node.children.size > 0) { node.children.forEach(childId => { const childConnections = computeNodeConnections(childId); childConnections.forEach((child, index) => { - result[index].colors = [...new Set([...result[index].colors, ...child.colors])]; + result[index].phenotypes = [...new Set([...result[index].phenotypes, ...child.phenotypes])]; result[index].ksIds = new Set([...result[index].ksIds, ...child.ksIds]); }); }); @@ -165,21 +165,12 @@ export function calculateSecondaryConnections( const knowledgeStatementIds = Array.from(node.destinationDetails[endOrganIRI]) .filter(ksId => ksId in knowledgeStatements); - if (knowledgeStatementIds.length > 0) { - const ksPhenotypes = knowledgeStatementIds.map(ksId => knowledgeStatements[ksId].phenotype).filter(phenotype => phenotype !== ''); - const phenotypeColorsSet = new Set(); - - const unknownFilter = phenotypes.find(p => p.label === 'other'); - ksPhenotypes.length === 0 ? phenotypeColorsSet.add(unknownFilter?.color || '') : - ksPhenotypes.map(phenotype => { - const phn = phenotypes.find(p => p.label === phenotype); - phn ? phenotypeColorsSet.add(phn.color) : phenotypeColorsSet.add(unknownFilter?.color || '') // FIXME: Could be a bug - }) - - const phenotypeColors = Array.from(phenotypeColorsSet) - result[index].colors = phenotypeColors - result[index].ksIds = new Set([...result[index].ksIds, ...knowledgeStatementIds]); - } + const ksPhenotypes = knowledgeStatementIds.map(ksId => { + return knowledgeStatements[ksId].phenotype ? knowledgeStatements[ksId].phenotype : OTHER_PHENOTYPE_LABEL + }) + + result[index].phenotypes = [...new Set(ksPhenotypes)]; + result[index].ksIds = new Set([...result[index].ksIds, ...knowledgeStatementIds]); } }); } @@ -187,7 +178,6 @@ export function calculateSecondaryConnections( memo.set(nodeId, result); return result; } - computeNodeConnections(hierarchyNode.id) return memo; }