Skip to content

Commit

Permalink
Merge pull request #538 from ProteinsWebTeam/representative-domains-e…
Browse files Browse the repository at this point in the history
…vetywhere

Representative domains everywhere
  • Loading branch information
gustavo-salazar authored Nov 20, 2023
2 parents 664c4ff + 99b2c42 commit 67e5135
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 69 deletions.
5 changes: 4 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ overrides:
- files: "*.css"
options:
singleQuote: false
- files: "src/**/*.js"
- files:
- "src/**/*.js"
- "src/**/*.ts"
- "src/**/*.tsx"
options:
parser: typescript
trailingComma: all
29 changes: 22 additions & 7 deletions src/components/Protein/Isoforms/Viewer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import descriptionToPath from 'utils/processDescription/descriptionToPath';
import NumberComponent from 'components/NumberComponent';
import { groupByEntryType } from 'components/Related/DomainsOnProtein';
import { byEntryType } from 'components/Related/DomainsOnProtein/DomainsOnProteinLoaded';
import { selectRepresentativeDomains } from 'components/ProteinViewer/utils';
import Loading from 'components/SimpleCommonComponents/Loading';

import loadable from 'higherOrder/loadable';
Expand Down Expand Up @@ -58,17 +59,31 @@ const features2protvista = (features: FeatureMap) => {
}

const interpro = featArray.filter(({ accession }) =>
accession.toLowerCase().startsWith('ipr')
accession.toLowerCase().startsWith('ipr'),
);
const groups = groupByEntryType(interpro);
const unintegrated = featArray.filter(
(f) => interpro.indexOf(f) === -1 && integrated.indexOf(f) === -1
(f) => interpro.indexOf(f) === -1 && integrated.indexOf(f) === -1,
);
const categories: Array<[string, unknown]> = [
...(Object.entries(groups) as Array<[string, unknown]>),
['unintegrated', unintegrated],
];
return categories.sort(byEntryType);

const sortedCategories = categories.sort(byEntryType);
const representativeDomains = selectRepresentativeDomains(
featArray,
'locations',
);

if (representativeDomains?.length) {
sortedCategories.splice(0, 0, [
'representative domains',
representativeDomains,
]);
}

return sortedCategories;
};
type Props = {
isoform?: string;
Expand Down Expand Up @@ -113,7 +128,7 @@ const Viewer = ({ isoform, data }: LoadedProps) => {

const mapStateToProps = createSelector(
(state: GlobalState) => state.customLocation.search,
({ isoform }) => ({ isoform })
({ isoform }) => ({ isoform }),
);

const getIsoformURL = createSelector(
Expand All @@ -123,7 +138,7 @@ const getIsoformURL = createSelector(
(
{ protocol, hostname, port, root },
{ protein: { accession } },
{ isoform }
{ isoform },
) => {
const description = {
main: { key: 'protein' },
Expand All @@ -139,8 +154,8 @@ const getIsoformURL = createSelector(
isoforms: isoform,
},
});
}
},
);
export default loadData({ getUrl: getIsoformURL, mapStateToProps } as Params)(
Viewer
Viewer,
);
27 changes: 16 additions & 11 deletions src/components/ProteinViewer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ import { toPlural } from 'utils/pages/toPlural';
import { NOT_MEMBER_DBS } from 'menuConfig';
import { getTrackColor, EntryColorMode } from 'utils/entry-color';

const selectRepresentativeDomains = (domains: Record<string, unknown>[]) => {
export const selectRepresentativeDomains = (
domains: Record<string, unknown>[],
locationKey: string = 'entry_protein_locations',
) => {
const flatDomains = [];
for (const domain of domains) {
const { accession, short_name, name, source_database, integrated } = domain;
for (const location of domain.entry_protein_locations as Array<ProtVistaLocation>) {
const { accession, short_name, name, source_database, integrated, chain } =
domain;
for (const location of domain[locationKey] as Array<ProtVistaLocation>) {
for (const fragment of location.fragments) {
const { start, end, representative } = fragment;
if (representative) {
flatDomains.push({
accession,
chain,
short_name,
name,
source_database,
Expand All @@ -31,15 +36,15 @@ const selectRepresentativeDomains = (domains: Record<string, unknown>[]) => {
};
export const useProcessData = <M = Metadata>(
results: EndpointWithMatchesPayload<M, MatchI>[] | undefined,
endpoint: Endpoint
endpoint: Endpoint,
) =>
useMemo(() => {
return processData(results || [], endpoint);
}, [results, endpoint]);

const processData = <M = Metadata>(
dataResults: EndpointWithMatchesPayload<M>[],
endpoint: Endpoint
endpoint: Endpoint,
) => {
const results: Record<string, unknown>[] = [];
for (const item of dataResults) {
Expand All @@ -50,29 +55,29 @@ const processData = <M = Metadata>(
...match,
...item.metadata,
...(item.extra_fields || {}),
}))
})),
);
}
const interpro = results.filter(
(entry) =>
(entry as unknown as Metadata).source_database.toLowerCase() ===
'interpro'
'interpro',
);

const representativeDomains = selectRepresentativeDomains(results);
const interproMap = new Map(
interpro.map((ipro) => [
`${ipro.accession}-${ipro.chain}-${ipro.protein}`,
ipro,
])
]),
);
const integrated = results.filter((entry) => entry.integrated);
const unintegrated = results.filter(
(entry) =>
interpro.concat(integrated).indexOf(entry) === -1 &&
!NOT_MEMBER_DBS.has(
(entry as unknown as Metadata).source_database.toLowerCase()
)
(entry as unknown as Metadata).source_database.toLowerCase(),
),
);
integrated.forEach((entry) => {
const ipro: Record<string, unknown> & {
Expand All @@ -84,7 +89,7 @@ const processData = <M = Metadata>(
if (ipro.children.indexOf(entry) === -1) ipro.children.push(entry);
});
integrated.sort((a, b) =>
a.chain ? (a.chain as string).localeCompare(b.chain as string) : -1
a.chain ? (a.chain as string).localeCompare(b.chain as string) : -1,
);
return {
interpro,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
exports[`<EntriesOnStructure /> should render 1`] = `
<React.Fragment>
<div
className="row"
className="vf-stack vf-stack--400"
>
<div
className="columns"
className="vf-stack"
>
<h4
id="protvista-A-p00735"
Expand Down
52 changes: 43 additions & 9 deletions src/components/Related/DomainEntriesOnStructure/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';

import Link from 'components/generic/Link';
import Tooltip from 'components/SimpleCommonComponents/Tooltip';
Expand Down Expand Up @@ -50,7 +50,7 @@ export type DataForProteinChain = {

const mergeData = (
secondaryData: StructureLinkedObject[],
secondaryStructures?: SecondaryStructure[]
secondaryStructures?: SecondaryStructure[],
) => {
const out: Record<string, Record<string, DataForProteinChain>> = {};
for (const entry of secondaryData) {
Expand Down Expand Up @@ -119,7 +119,7 @@ const mergeData = (
({ entry_protein_locations, entry_structure_locations, ...child }) => ({
...child,
locations: entry_structure_locations,
})
}),
),
chain: entry.chain,
protein: entry.protein,
Expand All @@ -131,7 +131,7 @@ const mergeData = (
const chains = Object.keys(out).sort((a, b) => (a ? a.localeCompare(b) : -1));
for (const chain of chains) {
const proteins = Object.keys(out[chain]).sort((a, b) =>
a ? a.localeCompare(b) : -1
a ? a.localeCompare(b) : -1,
);
for (const protein of proteins) {
entries.push(out[chain][protein]);
Expand All @@ -151,37 +151,71 @@ const tagChimericStructures = (data: DataForProteinChain[]) => {
}
};

const getRepresentativesPerChain = (
representativeDomains?: Record<string, unknown>[],
) => {
const representativesPerChain: Record<string, Array<MinimalFeature>> = {};
if (representativeDomains?.length) {
representativeDomains.forEach((domain) => {
if (domain.chain) {
if (!representativesPerChain[domain.chain as string])
representativesPerChain[domain.chain as string] = [];
representativesPerChain[domain.chain as string].push(
domain as MinimalFeature,
);
}
});
}
return representativesPerChain;
};

type Props = {
structure: string;
entries: StructureLinkedObject[];
secondaryStructures?: SecondaryStructure[];
representativeDomains?: Record<string, unknown>[];
};

const EntriesOnStructure = ({
entries,
secondaryStructures,
structure,
representativeDomains,
}: Props) => {
const merged = mergeData(entries, secondaryStructures);
tagChimericStructures(merged);
const merged = useMemo(() => {
const data = mergeData(entries, secondaryStructures);
tagChimericStructures(data);
return data;
}, [entries, secondaryStructures]);
const representativesPerChain = useMemo(
() => getRepresentativesPerChain(representativeDomains),
[representativeDomains],
);

return (
<>
<div className={css('row')}>
<div className={css('vf-stack', 'vf-stack--400')}>
{merged.map((e, i) => {
const sequenceData = {
accession: `${e.chain}-${structure}`,
...e.sequence,
};

const tracks = Object.entries(
e.data as Record<string, Array<Record<string, unknown>>>
e.data as Record<string, Array<Record<string, unknown>>>,
).sort(([a], [b]) => {
if (a && a.toLowerCase() === 'chain') return -1;
if (b && b.toLowerCase() === 'chain') return 1;
return b ? b.localeCompare(a) : -1;
});
if (representativesPerChain[e.chain]) {
tracks.splice(0, 0, [
'representative domains',
representativesPerChain[e.chain],
]);
}
return (
<div key={i} className={css('columns')}>
<div key={i} className={css('vf-stack')}>
<h4 id={`protvista-${e.chain}-${e.protein.accession}`}>
Chain {e.chain}{' '}
{e.protein?.accession && (
Expand Down
Loading

0 comments on commit 67e5135

Please sign in to comment.