Skip to content
Snippets Groups Projects
Commit 43f630c5 authored by Adrian Orłów's avatar Adrian Orłów
Browse files

feat: add overlay chart in bioentity drawer

parent dd0d629f
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!124feat: add overlay chart in bioentity drawer (MIN-236)
Pipeline #85371 passed
Showing
with 746 additions and 7 deletions
......@@ -4,6 +4,7 @@ import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { DrawerHeading } from '@/shared/DrawerHeading';
import { AnnotationItem } from './AnnotationItem';
import { AssociatedSubmap } from './AssociatedSubmap';
import { OverlayData } from './OverlayData';
export const BioEntityDrawer = (): React.ReactNode => {
const bioEntityData = useAppSelector(searchedFromMapBioEntityElement);
......@@ -15,7 +16,7 @@ export const BioEntityDrawer = (): React.ReactNode => {
const isReferenceAvailable = bioEntityData.references.length > ZERO;
return (
<div className="h-full max-h-full" data-testid="bioentity-drawer">
<div className="h-calc-drawer" data-testid="bioentity-drawer">
<DrawerHeading
title={
<>
......@@ -24,7 +25,7 @@ export const BioEntityDrawer = (): React.ReactNode => {
</>
}
/>
<div className="flex flex-col gap-6 p-6">
<div className="flex max-h-full flex-col gap-6 overflow-y-auto p-6">
<div className="text-sm font-normal">
Compartment: <b className="font-semibold">{bioEntityData.compartmentName}</b>
</div>
......@@ -47,6 +48,7 @@ export const BioEntityDrawer = (): React.ReactNode => {
/>
))}
<AssociatedSubmap />
<OverlayData />
</div>
</div>
);
......
import { GENE_VARIANTS_MOCK } from '@/models/mocks/geneVariantsMock';
import { GeneVariant } from '@/types/models';
import { render, screen } from '@testing-library/react';
import { GeneVariantsTable } from './GeneVariantsTable.component';
const renderComponent = (data: GeneVariant[]): void => {
render(<GeneVariantsTable data={data} />);
};
describe('GeneVariantsTable - component', () => {
beforeEach(() => {
renderComponent(GENE_VARIANTS_MOCK);
});
it('should render header', () => {
expect(screen.getByText('Contig')).toBeInTheDocument();
expect(screen.getByText('Position')).toBeInTheDocument();
expect(screen.getByText('From')).toBeInTheDocument();
expect(screen.getByText('To')).toBeInTheDocument();
expect(screen.getByText('rsID')).toBeInTheDocument();
});
it.each(GENE_VARIANTS_MOCK)(
'should render row',
({ contig, position, originalDna, modifiedDna, variantIdentifier }) => {
const elements = [
screen.getAllByText(contig),
screen.getAllByText(position),
screen.getAllByText(originalDna),
screen.getAllByText(modifiedDna),
screen.getAllByText(variantIdentifier),
].flat();
elements.forEach(element => expect(element).toBeInTheDocument());
},
);
});
import { TWO } from '@/constants/common';
import { GeneVariant } from '@/types/models';
import { twMerge } from 'tailwind-merge';
interface Props {
data: GeneVariant[];
}
export const GeneVariantsTable = ({ data }: Props): JSX.Element => {
return (
<table className="rounded-lg text-xs shadow-tableBorderDivide">
<tr className="border-b border-divide text-left text-[#6A6977]">
<th className="py-4 pl-4 pt-5 font-light ">Contig</th>
<th className="py-4 pt-5 font-light">Position</th>
<th className="py-4 pt-5 font-light">From</th>
<th className="py-4 pt-5 font-light">To</th>
<th className="py-4 pr-4 pt-5 font-light">rsID</th>
</tr>
{data.map((variant, index) => {
const isOdd = index % TWO;
const isEven = !isOdd;
return (
<tr key={variant.position} className={twMerge('font-semibold', isEven && 'bg-[#F3F3F3]')}>
<td className="py-4 pl-4">{variant.contig}</td>
<td className="py-4">{variant.position}</td>
<td className="py-4">{variant.originalDna}</td>
<td className="py-4">{variant.modifiedDna}</td>
<td className="py-4 pr-4">{variant.variantIdentifier}</td>
</tr>
);
})}
</table>
);
};
export { GeneVariantsTable } from './GeneVariantsTable.component';
/* eslint-disable no-magic-numbers */
import { ZERO } from '@/constants/common';
import { GENE_VARIANTS_MOCK } from '@/models/mocks/geneVariantsMock';
import { act, render, screen } from '@testing-library/react';
import { OverlayDataAxis } from '../OverlayData.types';
import { OverlayAxis } from './OverlayAxis.component';
const renderComponent = (axis: OverlayDataAxis): void => {
render(<OverlayAxis axis={axis} />);
};
const BASE_AXIS: OverlayDataAxis = {
id: 123,
title: 'axis title',
value: 2137,
color: '#FFFFFF',
geneVariants: undefined,
};
describe('OverlayAxis - component', () => {
describe('when always', () => {
beforeEach(() => {
renderComponent(BASE_AXIS);
});
it('renders title', () => {
expect(screen.getByText('axis title')).toBeInTheDocument();
});
it('renders background with valid color', () => {
expect(screen.getByTestId('overlay-axis-bg')).toBeInTheDocument();
expect(screen.getByTestId('overlay-axis-bg').getAttribute('style')).toContain(
'background: rgba(255, 255, 255, 0.102);',
);
});
it('renders valid value title (when no gene variants)', () => {
expect(screen.getByTestId('overlay-axis-value')).toBeInTheDocument();
expect(screen.getByTestId('overlay-axis-value').innerHTML).toContain('2137');
});
});
describe('when value is positive', () => {
beforeEach(() => {
renderComponent({
...BASE_AXIS,
value: 0.564234344,
color: '#FF0000',
});
});
it('renders bar with valid color and width', () => {
const axis = screen.getByTestId('overlay-axis-bg');
const bar = axis.childNodes[0] as HTMLElement;
expect(bar).toBeInTheDocument();
expect(bar?.getAttribute('class')).toContain('rounded-r');
expect(bar?.getAttribute('class')).toContain('left-1/2');
expect(bar?.getAttribute('style')).toContain('width: 0.028211717200000003%;');
expect(bar?.getAttribute('style')).toContain('background: rgb(255, 0, 0);');
});
it('renders valid value title (when no gene variants)', () => {
expect(screen.getByTestId('overlay-axis-value')).toBeInTheDocument();
expect(screen.getByTestId('overlay-axis-value').innerHTML).toContain('0.56');
});
});
describe('when value is negative', () => {
beforeEach(() => {
renderComponent({
...BASE_AXIS,
value: -0.3255323223,
color: '#00FF00',
});
});
it('renders bar with valid color and width', () => {
const axis = screen.getByTestId('overlay-axis-bg');
const bar = axis.childNodes[0] as HTMLElement;
expect(bar).toBeInTheDocument();
expect(bar?.getAttribute('class')).toContain('rounded-l');
expect(bar?.getAttribute('class')).toContain('right-1/2');
expect(bar?.getAttribute('style')).toContain('width: 0.016276616115%;');
expect(bar?.getAttribute('style')).toContain('background: rgb(0, 255, 0);');
});
it('renders valid value title (when no gene variants)', () => {
expect(screen.getByTestId('overlay-axis-value')).toBeInTheDocument();
expect(screen.getByTestId('overlay-axis-value').innerHTML).toContain('-0.33');
});
});
describe('when value is zero', () => {
beforeEach(() => {
renderComponent({
...BASE_AXIS,
value: 0,
color: '#0000FF',
});
});
it('renders bar with valid color and width', () => {
const axis = screen.getByTestId('overlay-axis-bg');
const bar = axis.childNodes[0] as HTMLElement;
expect(bar).toBeInTheDocument();
expect(bar?.getAttribute('class')).not.toContain('right-1/2');
expect(bar?.getAttribute('class')).not.toContain('rounded-l');
expect(bar?.getAttribute('class')).not.toContain('right-1/2');
expect(bar?.getAttribute('style')).toContain('width: 0%;');
});
it('renders valid value title (when no gene variants)', () => {
expect(screen.getByTestId('overlay-axis-value')).toBeInTheDocument();
expect(screen.getByTestId('overlay-axis-value').innerHTML).toContain('-');
});
});
describe('when value is undefined', () => {
beforeEach(() => {
renderComponent({
...BASE_AXIS,
value: undefined,
});
});
it('renders bar with valid color and width', () => {
const axis = screen.getByTestId('overlay-axis-bg');
const bar = axis.childNodes[0] as HTMLElement;
expect(bar).toBeInTheDocument();
expect(bar?.getAttribute('class')).toContain('w-full');
expect(bar?.getAttribute('style')).toContain('width: 100%;');
expect(bar?.getAttribute('style')).toContain('background: rgb(255, 255, 255);');
});
it('renders valid value title (when no gene variants)', () => {
expect(screen.getByTestId('overlay-axis-value')).toBeInTheDocument();
expect(screen.getByTestId('overlay-axis-value').innerHTML).toContain('-');
});
});
describe('when there is gene variants', () => {
beforeEach(() => {
renderComponent({
...BASE_AXIS,
geneVariants: GENE_VARIANTS_MOCK,
});
});
it('renders gene variants icon button', () => {
expect(screen.getByTestId('overlay-axis-icon')).toBeInTheDocument();
});
it('renders gene variants info icon', () => {
const infoIconWrapper = screen.getByTitle(
'Number of variants mapped to this gene. See their details in the tabular view below.',
);
const icon = infoIconWrapper.childNodes[1] as HTMLElement;
expect(infoIconWrapper).toBeInTheDocument();
expect(icon).toBeInTheDocument();
});
it('shows gene variants table on gene variants icon button click', () => {
const geneVariantsTable = screen.queryAllByText('Contig');
expect(geneVariantsTable.length).toEqual(ZERO);
const iconButton = screen.getByTestId('overlay-axis-icon');
act(() => {
iconButton.click();
});
const geneVariantsTableVisible = screen.getByText('Contig');
expect(geneVariantsTableVisible).toBeInTheDocument();
});
});
});
import { Icon } from '@/shared/Icon';
import { IconButton } from '@/shared/IconButton';
import { useState } from 'react';
import { twMerge } from 'tailwind-merge';
import { OverlayDataAxis } from '../OverlayData.types';
import { GeneVariantsTable } from './GeneVariantsTable';
import { getOverlayAxisData } from './utils/getOverlayAxisProps';
interface Props {
axis: OverlayDataAxis;
}
export const OverlayAxis = ({ axis }: Props): JSX.Element => {
const [showGeneVariants, setShowGeneVariants] = useState<boolean>(false);
const { title, background, bar, value } = getOverlayAxisData(axis);
const toggleShowGeneVariants = (): void => {
setShowGeneVariants(v => !v);
};
return (
<>
<div className="flex items-center gap-2 text-xs">
<div className="flex w-48 items-center justify-between gap-2 font-semibold">
<div>{title}</div>
{axis.geneVariants && (
<IconButton
icon={showGeneVariants ? 'chevron-up' : 'chevron-down'}
data-testid="overlay-axis-icon"
className="h-6 w-6 flex-shrink-0 bg-transparent p-0"
onClick={toggleShowGeneVariants}
/>
)}
</div>
<div
className="relative h-6 w-full overflow-hidden rounded"
style={{ background: background.color }}
data-testid="overlay-axis-bg"
>
<div
className={twMerge(
'absolute h-full',
value.isPositive && 'left-1/2 rounded-r',
value.isNegative && 'right-1/2 rounded-l',
value.isUndefined && 'w-full',
)}
style={{ background: bar.color, width: `${bar.percentage}%` }}
/>
</div>
<div
className="flex h-6 w-12 flex-shrink-0 items-center justify-center rounded border border-divide p-1 text-center font-semibold"
title={
axis.geneVariants
? 'Number of variants mapped to this gene. See their details in the tabular view below.'
: undefined
}
data-testid="overlay-axis-value"
>
{value.title}
{axis.geneVariants && (
<span className="ml-[2px] flex">
<Icon name="info" className="h-3 w-3 fill-black" />
</span>
)}
</div>
</div>
{axis.geneVariants && showGeneVariants && <GeneVariantsTable data={axis.geneVariants} />}
</>
);
};
export { OverlayAxis } from './OverlayAxis.component';
import { ONE_DECIMAL, ONE_HUNDRED, TWO, ZERO } from '@/constants/common';
import { HALF } from '@/constants/dividers';
import { addAlphaToHexString } from '@/utils/convert/addAlphaToHexString';
import { OverlayDataAxis } from '../../OverlayData.types';
interface OverlayAxisProps {
title: string;
background: {
color: string;
};
bar: {
color: string;
percentage: number;
};
value: {
title: string;
isPositive: boolean;
isNegative: boolean;
isUndefined: boolean;
};
}
const FULL_WIDTH = 100;
const DEFAULT_VALUE_TITLE = '-';
const getBarPercentage = (value?: number): number => {
const valueNormalized = value || ZERO;
const isValueUndefined = typeof value === 'undefined';
const valuePositivePercentage = Math.abs(valueNormalized) * ONE_HUNDRED;
const valuePositivePercentageHalf = valuePositivePercentage / HALF; // 100% = full width of posivie OR negative chart SIDE
return isValueUndefined ? FULL_WIDTH : valuePositivePercentageHalf; // axis without value = 100% both sides width
};
const getValueTitle = (axis: OverlayDataAxis): string => {
if (axis?.geneVariants) {
return axis.geneVariants.length.toString();
}
return axis.value ? axis.value.toFixed(TWO) : DEFAULT_VALUE_TITLE;
};
export const getOverlayAxisData = (axis: OverlayDataAxis): OverlayAxisProps => {
return {
title: axis.title,
background: {
color: addAlphaToHexString(axis.color, ONE_DECIMAL),
},
bar: {
color: axis.color,
percentage: getBarPercentage(axis.value),
},
value: {
title: getValueTitle(axis),
isUndefined: typeof axis.value === 'undefined',
isPositive: axis.value ? axis.value > ZERO : false,
isNegative: axis.value ? axis.value < ZERO : false,
},
};
};
/* eslint-disable no-magic-numbers */
import { ZERO } from '@/constants/common';
import { bioEntitiesContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
import { overlayFixture } from '@/models/fixtures/overlaysFixture';
import { GENE_VARIANTS_MOCK } from '@/models/mocks/geneVariantsMock';
import { CORE_PD_MODEL_MOCK } from '@/models/mocks/modelsMock';
import { initialMapStateFixture } from '@/redux/map/map.fixtures';
import { MODELS_INITIAL_STATE_MOCK } from '@/redux/models/models.mock';
import {
MOCKED_OVERLAY_BIO_ENTITY_RENDER,
OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK,
} from '@/redux/overlayBioEntity/overlayBioEntity.mock';
import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
import { StoreType } from '@/redux/store';
import { InitialStoreState } from '@/utils/testing/getReduxStoreActionsListener';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { render, screen } from '@testing-library/react';
import { OverlayData } from './OverlayData.component';
const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
return (
render(
<Wrapper>
<OverlayData />
</Wrapper>,
),
{
store,
}
);
};
describe('OverlayData - component', () => {
describe('when axes list is empty', () => {
beforeEach(() => {
renderComponent({});
});
it('should not render component', () => {
expect(screen.queryAllByText('Overlay data:').length).toEqual(ZERO);
});
});
describe('when axes list is present', () => {
beforeEach(() => {
const OVERLAY_ID = overlayFixture.idObject;
const BIO_ENTITY = MOCKED_OVERLAY_BIO_ENTITY_RENDER[0];
renderComponent({
...INITIAL_STORE_STATE_MOCK,
map: {
...initialMapStateFixture,
data: { ...initialMapStateFixture.data, modelId: CORE_PD_MODEL_MOCK.idObject },
},
overlays: {
...INITIAL_STORE_STATE_MOCK.overlays,
data: [{ ...overlayFixture, name: 'axis name' }],
},
bioEntity: {
data: [
{
searchQueryElement: '',
loading: 'pending',
error: { name: '', message: '' },
data: [
{
...bioEntitiesContentFixture[0],
bioEntity: {
...bioEntitiesContentFixture[0].bioEntity,
id: BIO_ENTITY.id,
},
},
],
},
],
loading: 'pending',
error: { name: '', message: '' },
},
overlayBioEntity: {
...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK,
overlaysId: [OVERLAY_ID],
data: {
[OVERLAY_ID]: {
[CORE_PD_MODEL_MOCK.idObject]: [
{
...BIO_ENTITY,
geneVariants: GENE_VARIANTS_MOCK,
modelId: CORE_PD_MODEL_MOCK.idObject,
overlayId: OVERLAY_ID,
},
],
},
},
},
models: { ...MODELS_INITIAL_STATE_MOCK, data: [CORE_PD_MODEL_MOCK] },
drawer: {
...INITIAL_STORE_STATE_MOCK.drawer,
bioEntityDrawerState: {
...INITIAL_STORE_STATE_MOCK.drawer.bioEntityDrawerState,
bioentityId: BIO_ENTITY.id,
},
},
});
});
it('should render title', () => {
expect(screen.getByText('Overlay data:')).toBeInTheDocument();
});
it('should render axis title', () => {
expect(screen.getByText('axis name')).toBeInTheDocument();
});
});
});
import { ZERO } from '@/constants/common';
import { OverlayAxis } from './OverlayAxis';
import { useOverlaysAxes } from './utils/useOverlaysAxes';
export const OverlayData = (): JSX.Element | null => {
const axes = useOverlaysAxes();
if (axes.length === ZERO) {
return null;
}
return (
<div>
<h3 className="mb-2 font-semibold">Overlay data:</h3>
<div className="flex flex-col gap-2 rounded-lg border border-divide p-4">
{axes.map(axis => (
<OverlayAxis key={axis.title} axis={axis} />
))}
</div>
</div>
);
};
import { GeneVariant } from '@/types/models';
export interface OverlayDataAxis {
id: number;
title: string;
value?: number;
color: string;
geneVariants?: GeneVariant[] | null;
}
export { OverlayData } from './OverlayData.component';
import { useGetOverlayColor } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor';
import { ONE } from '@/constants/common';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import {
overlaysBioEntityForCurrentBioEntityAndCurrentModelSelector,
overlaysOpenedSelector,
} from '@/redux/overlayBioEntity/overlayBioEntity.selector';
import { useSelector } from 'react-redux';
import { OverlayDataAxis } from '../OverlayData.types';
export const useOverlaysAxes = (): OverlayDataAxis[] => {
const openedOverlays = useSelector(overlaysOpenedSelector);
const currentBioEntityOverlaysForCurrentBioEntity = useAppSelector(
overlaysBioEntityForCurrentBioEntityAndCurrentModelSelector,
);
const { getOverlayBioEntityColorByAvailableProperties } = useGetOverlayColor({
forceOpacityValue: ONE,
});
return currentBioEntityOverlaysForCurrentBioEntity.map((overlayBioEntity): OverlayDataAxis => {
const overlay = openedOverlays.find(o => o.idObject === overlayBioEntity.overlayId);
return {
id: overlayBioEntity.id,
title: overlay?.name || '',
value: overlayBioEntity.value || undefined,
color: getOverlayBioEntityColorByAvailableProperties(overlayBioEntity),
geneVariants: overlayBioEntity?.geneVariants,
};
});
};
import { useCallback, useMemo } from 'react';
import { ONE, ZERO } from '@/constants/common';
import { WHITE_HEX_OPACITY_0 } from '@/constants/hexColors';
import {
maxColorValSelector,
......@@ -8,11 +8,11 @@ import {
simpleColorValSelector,
} from '@/redux/configuration/configuration.selectors';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { getHexTricolorGradientColorWithAlpha } from '@/utils/convert/getHexTricolorGradientColorWithAlpha';
import { ONE, ZERO } from '@/constants/common';
import { OverlayBioEntityRender } from '@/types/OLrendering';
import { addAlphaToHexString } from '@/utils/convert/addAlphaToHexString';
import { getHexStringColorFromRGBIntWithAlpha } from '@/utils/convert/getHexStringColorFromRGBIntWithAlpha';
import { getHexTricolorGradientColorWithAlpha } from '@/utils/convert/getHexTricolorGradientColorWithAlpha';
import { useCallback, useMemo } from 'react';
type GetOverlayBioEntityColorByAvailableProperties = (entity: OverlayBioEntityRender) => string;
......@@ -20,12 +20,17 @@ type UseTriColorLerpReturn = {
getOverlayBioEntityColorByAvailableProperties: GetOverlayBioEntityColorByAvailableProperties;
};
export const useGetOverlayColor = (): UseTriColorLerpReturn => {
interface Props {
forceOpacityValue?: number;
}
export const useGetOverlayColor = ({ forceOpacityValue }: Props = {}): UseTriColorLerpReturn => {
const minColorValHexString = useAppSelector(minColorValSelector) || '';
const maxColorValHexString = useAppSelector(maxColorValSelector) || '';
const neutralColorValHexString = useAppSelector(neutralColorValSelector) || '';
const overlayOpacityValue = useAppSelector(overlayOpacitySelector) || ONE;
const simpleColorValue = useAppSelector(simpleColorValSelector) || WHITE_HEX_OPACITY_0;
const overlayOpacityDefaultValue = useAppSelector(overlayOpacitySelector);
const overlayOpacityValue = forceOpacityValue || overlayOpacityDefaultValue || ONE;
const getHex3ColorGradientColorWithAlpha = useCallback(
(position: number) =>
......
......@@ -8,6 +8,11 @@ export const FIRST_ARRAY_ELEMENT = 0;
export const ONE = 1;
export const SECOND_ARRAY_ELEMENT = 1;
export const TWO = 2;
export const THIRD_ARRAY_ELEMENT = 2;
export const NOOP = (): void => {};
export const ONE_DECIMAL = 0.1;
export const ONE_HUNDRED = 0.1;
import { z } from 'zod';
export const geneVariant = z.object({
position: z.number(),
originalDna: z.string(),
modifiedDna: z.string(),
contig: z.string(),
allelFrequency: z.number(),
aminoAcidChange: z.string(),
variantIdentifier: z.string(),
});
import { GeneVariant } from '@/types/models';
export const GENE_VARIANTS_MOCK: GeneVariant[] = [
{
position: 162394349,
originalDna: 'G',
modifiedDna: 'A',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.162394349G>A:p.T240M',
variantIdentifier: 'rs137853054',
},
{
position: 161771219,
originalDna: 'G',
modifiedDna: 'A',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.161771219G>A:p.P437L',
variantIdentifier: 'rs149953814',
},
{
position: 162206852,
originalDna: 'G',
modifiedDna: 'A',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.162206852G>A:p.R275W',
variantIdentifier: 'rs34424986',
},
{
position: 162394349,
originalDna: 'G',
modifiedDna: 'C',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.162394349G>C:p.T240R',
variantIdentifier: 'rs137853054',
},
{
position: 161771171,
originalDna: 'C',
modifiedDna: 'T',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.161771171C>T:p.W453*',
variantIdentifier: 'rs137853056',
},
{
position: 162683694,
originalDna: 'G',
modifiedDna: 'A',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.162683694G>A:p.A92V',
variantIdentifier: 'rs566229879',
},
{
position: 162206914,
originalDna: 'T',
modifiedDna: 'C',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.162206914T>C:p.N254S',
variantIdentifier: 'rs139600787',
},
{
position: 162394349,
originalDna: 'G',
modifiedDna: 'C',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.162394349G>C:p.T240R',
variantIdentifier: 'rs137853054',
},
{
position: 162206825,
originalDna: 'C',
modifiedDna: 'G',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.162206825C>G:p.G284R',
variantIdentifier: 'rs751037529',
},
{
position: 162394338,
originalDna: 'C',
modifiedDna: 'T',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.162394338C>T:p.V244I',
variantIdentifier: 'rs771259513',
},
{
position: 161781201,
originalDna: 'G',
modifiedDna: 'A',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.161781201G>A:p.R402C',
variantIdentifier: 'rs55830907',
},
{
position: 162622239,
originalDna: 'G',
modifiedDna: 'C',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.162622239G>C:p.P153R',
variantIdentifier: 'rs55654276',
},
{
position: 161807855,
originalDna: 'C',
modifiedDna: 'G',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.161807855C>G:p.V380L',
variantIdentifier: 'rs1801582',
},
{
position: 161771237,
originalDna: 'C',
modifiedDna: 'A',
contig: 'chr6',
allelFrequency: 0.8,
aminoAcidChange: 'PRKN:NC_000006.11:g.161771237C>A:p.C431F',
variantIdentifier: 'rs397514694',
},
];
import { z } from 'zod';
import { colorSchema } from './colorSchema';
import { geneVariant } from './geneVariant';
export const overlayRightBioEntitySchema = z.object({
id: z.number(),
......@@ -11,4 +12,5 @@ export const overlayRightBioEntitySchema = z.object({
value: z.number().nullable(),
color: colorSchema.nullable(),
description: z.string().nullable(),
geneVariants: z.array(geneVariant).optional().nullable(),
});
import { OverlayBioEntityRender } from '@/types/OLrendering';
import { createSelector } from '@reduxjs/toolkit';
import { currentSearchedBioEntityId } from '../drawer/drawer.selectors';
import { currentModelIdSelector } from '../models/models.selectors';
import { overlaysDataSelector, overlaysIdsAndOrderSelector } from '../overlays/overlays.selectors';
import { rootSelector } from '../root/root.selectors';
......@@ -66,3 +67,20 @@ export const getOverlayOrderSelector = createSelector(
return calculateOvarlaysOrder(activeOverlaysIdsAndOrder);
},
);
export const overlaysOpenedIdsSelector = createSelector(
rootSelector,
state => state.overlayBioEntity.overlaysId,
);
export const overlaysOpenedSelector = createSelector(
overlaysDataSelector,
overlaysOpenedIdsSelector,
(data, ids) => data.filter(entity => ids.includes(entity.idObject)),
);
export const overlaysBioEntityForCurrentBioEntityAndCurrentModelSelector = createSelector(
overlayBioEntitiesForCurrentModelSelector,
currentSearchedBioEntityId,
(data, currentBioEntityId) => data.filter(entity => entity.id === currentBioEntityId),
);
......@@ -32,6 +32,7 @@ export const parseOverlayBioEntityToOlRenderingFormat = (
value: entity.right.value,
overlayId,
color: entity.right.color,
geneVariants: entity.right.geneVariants,
});
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment