Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Representative domains everywhere #538

Merged
merged 5 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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