diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayLineFeature.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayLineFeature.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..74f6839491421bf93129b446cc6d543636b7961f --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayLineFeature.test.ts @@ -0,0 +1,91 @@ +import { LINE_WIDTH } from '@/constants/canvas'; +import { initialMapStateFixture } from '@/redux/map/map.fixtures'; +import { LinePoint } from '@/types/reactions'; +import { usePointToProjection } from '@/utils/map/usePointToProjection'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { renderHook } from '@testing-library/react'; +import { Feature } from 'ol'; +import { Geometry } from 'ol/geom'; +import { createOverlayLineFeature } from './createOverlayLineFeature'; + +/* eslint-disable no-magic-numbers */ +const CASES: [LinePoint, number[]][] = [ + [ + [ + { x: 0, y: 0 }, + { x: 0, y: 0 }, + ], + [0, 0, 0, 0], + ], + [ + [ + { x: 0, y: 0 }, + { x: 100, y: 100 }, + ], + [0, -238107693, Infinity, 0], + ], + [ + [ + { x: 100, y: 100 }, + { x: 0, y: 0 }, + ], + [0, -238107693, Infinity, 0], + ], + [ + [ + { x: 100, y: 0 }, + { x: 0, y: 100 }, + ], + [0, 0, 0, 0], + ], + [ + [ + { x: -50, y: 0 }, + { x: 0, y: -50 }, + ], + [0, 0, 0, 0], + ], +]; + +const COLOR = '#FFB3B3cc'; + +const getFeature = (linePoint: LinePoint): Feature<Geometry> => { + const { Wrapper } = getReduxWrapperWithStore({ + map: initialMapStateFixture, + }); + const { + result: { current: pointToProjection }, + } = renderHook(() => usePointToProjection(), { + wrapper: Wrapper, + }); + + return createOverlayLineFeature(linePoint, { pointToProjection, color: COLOR }); +}; + +describe('createOverlayLineFeature - util', () => { + it.each(CASES)('should return Feature instance', linePoint => { + const feature = getFeature(linePoint); + + expect(feature).toBeInstanceOf(Feature); + }); + + it.each(CASES)('should return Feature instance with valid style and stroke', linePoint => { + const feature = getFeature(linePoint); + const style = feature.getStyle(); + + expect(style).toMatchObject({ + fill_: { color_: COLOR }, + stroke_: { + color_: COLOR, + width_: LINE_WIDTH, + }, + }); + }); + + it.each(CASES)('should return Feature instance with valid geometry', (linePoint, extent) => { + const feature = getFeature(linePoint); + const geometry = feature.getGeometry(); + + expect(geometry?.getExtent()).toEqual(extent); + }); +}); diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayLineFeature.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayLineFeature.ts new file mode 100644 index 0000000000000000000000000000000000000000..b38d603bbb793c6578722f2c8170cd8f5c895b51 --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayLineFeature.ts @@ -0,0 +1,22 @@ +import { LinePoint } from '@/types/reactions'; +import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection'; +import { Feature } from 'ol'; +import { SimpleGeometry } from 'ol/geom'; +import { getLineFeature } from '../reactionsLayer/getLineFeature'; +import { getOverlayLineFeatureStyle } from './getOverlayLineFeatureStyle'; + +interface Options { + color: string; + pointToProjection: UsePointToProjectionResult; +} + +export const createOverlayLineFeature = ( + points: LinePoint, + { color, pointToProjection }: Options, +): Feature<SimpleGeometry> => { + const feature = getLineFeature(points, pointToProjection); + + feature.setStyle(getOverlayLineFeatureStyle(color)); + + return feature; +}; diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlayLineFeatureStyle.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlayLineFeatureStyle.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..174c620d5c12b95a9033da7cf11a04a942302c91 --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlayLineFeatureStyle.test.ts @@ -0,0 +1,27 @@ +import { LINE_WIDTH } from '@/constants/canvas'; +import { Fill, Stroke, Style } from 'ol/style'; +import { getOverlayLineFeatureStyle } from './getOverlayLineFeatureStyle'; + +const COLORS = ['#000000', '#FFFFFF', '#F5F5F5', '#C0C0C0', '#C0C0C0aa', '#C0C0C0bb']; + +describe('getOverlayLineFeatureStyle - util', () => { + it.each(COLORS)('should return Style object', color => { + const result = getOverlayLineFeatureStyle(color); + expect(result).toBeInstanceOf(Style); + }); + + it.each(COLORS)('should set valid color values for Fill', color => { + const result = getOverlayLineFeatureStyle(color); + const fill = result.getFill(); + expect(fill).toBeInstanceOf(Fill); + expect(fill?.getColor()).toBe(color); + }); + + it.each(COLORS)('should set valid color values for Fill', color => { + const result = getOverlayLineFeatureStyle(color); + const stroke = result.getStroke(); + expect(stroke).toBeInstanceOf(Stroke); + expect(stroke?.getColor()).toBe(color); + expect(stroke?.getWidth()).toBe(LINE_WIDTH); + }); +}); diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlayLineFeatureStyle.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlayLineFeatureStyle.ts new file mode 100644 index 0000000000000000000000000000000000000000..407c2416bbcee994a3d954d866520e0f2d65064c --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlayLineFeatureStyle.ts @@ -0,0 +1,5 @@ +import { LINE_WIDTH } from '@/constants/canvas'; +import { Fill, Stroke, Style } from 'ol/style'; + +export const getOverlayLineFeatureStyle = (color: string): Style => + new Style({ fill: new Fill({ color }), stroke: new Stroke({ color, width: LINE_WIDTH }) }); diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor.test.ts index b580a998904b2975b82c0a527aa805c748c38288..61841611e83b4818cf7c1382cc0ff8795e85996e 100644 --- a/src/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor.test.ts +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useGetOverlayColor.test.ts @@ -1,7 +1,7 @@ -import { renderHook } from '@testing-library/react'; -import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; import { CONFIGURATION_INITIAL_STORE_MOCKS } from '@/redux/configuration/configuration.mock'; import { OverlayBioEntityRender } from '@/types/OLrendering'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { renderHook } from '@testing-library/react'; import { useGetOverlayColor } from './useGetOverlayColor'; describe('useOverlayFeatures - hook', () => { @@ -20,6 +20,7 @@ describe('useOverlayFeatures - hook', () => { describe('getOverlayBioEntityColorByAvailableProperties - function', () => { const ENTITY: OverlayBioEntityRender = { + type: 'rectangle', id: 0, modelId: 0, x1: 0, diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts index 19c9b309415c6943c7c86e8b1fe477c606affe81..15c9cf3e5e9820d3d3f9d604bc19a7e5dad1c383 100644 --- a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts @@ -1,11 +1,11 @@ /* eslint-disable no-magic-numbers */ -import { renderHook } from '@testing-library/react'; -import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; import { CONFIGURATION_INITIAL_STORE_MOCKS } from '@/redux/configuration/configuration.mock'; -import { OVERLAYS_PUBLIC_FETCHED_STATE_MOCK } from '@/redux/overlays/overlays.mock'; import { mapStateWithCurrentlySelectedMainMapFixture } from '@/redux/map/map.fixtures'; import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock'; import { MOCKED_OVERLAY_BIO_ENTITY_RENDER } from '@/redux/overlayBioEntity/overlayBioEntity.mock'; +import { OVERLAYS_PUBLIC_FETCHED_STATE_MOCK } from '@/redux/overlays/overlays.mock'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { renderHook } from '@testing-library/react'; import { useOverlayFeatures } from './useOverlayFeatures'; /** @@ -52,7 +52,9 @@ describe('useOverlayFeatures', () => { wrapper: Wrapper, }); - expect(features).toHaveLength(6); + expect(features).toHaveLength(10); + + // type: rectangle expect(features[0].getGeometry()?.getCoordinates()).toEqual([ [ [-13149141, 18867005], @@ -65,5 +67,19 @@ describe('useOverlayFeatures', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore expect(features[0].getStyle().getFill().getColor()).toBe('#FFFFFFcc'); + + // type: line + expect(features[7].getGeometry()?.getCoordinates()).toEqual([ + [ + [-13149141, 18867005], + [-13149141, 18881970], + [-13141659, 18881970], + [-13141659, 18867005], + [-13149141, 18867005], + ], + ]); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(features[7].getStyle().getFill().getColor()).toBe('#ff0000cc'); }); }); diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts index 06985195eb9dafe82dbf23f616b3214945c9aff2..477a95dd324f06f8099d88db81055e7324b77d5f 100644 --- a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.ts @@ -1,18 +1,21 @@ -import { useMemo } from 'react'; -import { usePointToProjection } from '@/utils/map/usePointToProjection'; -import type Feature from 'ol/Feature'; -import type Polygon from 'ol/geom/Polygon'; import { ZERO } from '@/constants/common'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { getOverlayOrderSelector, overlayBioEntitiesForCurrentModelSelector, } from '@/redux/overlayBioEntity/overlayBioEntity.selector'; +import { LinePoint } from '@/types/reactions'; +import { usePointToProjection } from '@/utils/map/usePointToProjection'; +import type Feature from 'ol/Feature'; +import { SimpleGeometry } from 'ol/geom'; +import type Polygon from 'ol/geom/Polygon'; +import { useMemo } from 'react'; import { createOverlayGeometryFeature } from './createOverlayGeometryFeature'; +import { createOverlayLineFeature } from './createOverlayLineFeature'; import { getPolygonLatitudeCoordinates } from './getPolygonLatitudeCoordinates'; import { useGetOverlayColor } from './useGetOverlayColor'; -export const useOverlayFeatures = (): Feature<Polygon>[] => { +export const useOverlayFeatures = (): Feature<Polygon>[] | Feature<SimpleGeometry>[] => { const pointToProjection = usePointToProjection(); const { getOverlayBioEntityColorByAvailableProperties } = useGetOverlayColor(); const overlaysOrder = useAppSelector(getOverlayOrderSelector); @@ -34,12 +37,27 @@ export const useOverlayFeatures = (): Feature<Polygon>[] => { overlaysOrder.find(({ id }) => id === entity.overlayId)?.index || ZERO, }); - return createOverlayGeometryFeature( + const color = getOverlayBioEntityColorByAvailableProperties(entity); + + if (entity.type === 'rectangle') { + return createOverlayGeometryFeature( + [ + ...pointToProjection({ x: xMin, y: entity.y1 }), + ...pointToProjection({ x: xMax, y: entity.y2 }), + ], + color, + ); + } + + return createOverlayLineFeature( [ - ...pointToProjection({ x: xMin, y: entity.y1 }), - ...pointToProjection({ x: xMax, y: entity.y2 }), - ], - getOverlayBioEntityColorByAvailableProperties(entity), + { x: entity.x1, y: entity.y1 }, + { x: entity.x2, y: entity.y2 }, + ] as LinePoint, + { + color, + pointToProjection, + }, ); }), [overlaysOrder, bioEntities, pointToProjection, getOverlayBioEntityColorByAvailableProperties], diff --git a/src/components/Map/MapViewer/utils/config/reactionsLayer/getLineFeature.ts b/src/components/Map/MapViewer/utils/config/reactionsLayer/getLineFeature.ts index 40211171605c35ff87995e6ed9abf02bc300c76b..b7ce166ad5bf96b39976fc6d08fabb8fae5e6f1a 100644 --- a/src/components/Map/MapViewer/utils/config/reactionsLayer/getLineFeature.ts +++ b/src/components/Map/MapViewer/utils/config/reactionsLayer/getLineFeature.ts @@ -1,12 +1,12 @@ import { LinePoint } from '@/types/reactions'; import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection'; import { Feature } from 'ol'; -import { LineString } from 'ol/geom'; +import { LineString, SimpleGeometry } from 'ol/geom'; export const getLineFeature = ( linePoints: LinePoint, pointToProjection: UsePointToProjectionResult, -): Feature => { +): Feature<SimpleGeometry> => { const points = linePoints.map(pointToProjection); return new Feature({ diff --git a/src/models/fixtures/overlayBioEntityFixture.ts b/src/models/fixtures/overlayBioEntityFixture.ts index da0c6da654ba996874863860034ebf6856762054..4cdeaebfde4d08c304d56de8d8db889a385b7c85 100644 --- a/src/models/fixtures/overlayBioEntityFixture.ts +++ b/src/models/fixtures/overlayBioEntityFixture.ts @@ -2,9 +2,21 @@ import { ZOD_SEED } from '@/constants'; import { z } from 'zod'; // eslint-disable-next-line import/no-extraneous-dependencies import { createFixture } from 'zod-fixture'; -import { overlayBioEntitySchema } from '../overlayBioEntitySchema'; +import { + overlayBioEntitySchema, + overlayElementWithBioEntitySchema, + overlayElementWithReactionSchema, +} from '../overlayBioEntitySchema'; export const overlayBioEntityFixture = createFixture(z.array(overlayBioEntitySchema), { seed: ZOD_SEED, array: { min: 3, max: 3 }, }); + +export const overlayElementWithReactionFixture = createFixture(overlayElementWithReactionSchema, { + seed: ZOD_SEED, +}); + +export const overlayElementWithBioEntityFixture = createFixture(overlayElementWithBioEntitySchema, { + seed: ZOD_SEED, +}); diff --git a/src/models/overlayBioEntitySchema.ts b/src/models/overlayBioEntitySchema.ts index d9dd58950b85a6d21bdde12e38e668af4d9b30e4..ffa8f5847d578da84236314ce036d5bc425d123c 100644 --- a/src/models/overlayBioEntitySchema.ts +++ b/src/models/overlayBioEntitySchema.ts @@ -1,8 +1,19 @@ import { z } from 'zod'; import { overlayLeftBioEntitySchema } from './overlayLeftBioEntitySchema'; +import { overlayLeftReactionSchema } from './overlayLeftReactionSchema'; import { overlayRightBioEntitySchema } from './overlayRightBioEntitySchema'; -export const overlayBioEntitySchema = z.object({ +export const overlayElementWithBioEntitySchema = z.object({ left: overlayLeftBioEntitySchema, right: overlayRightBioEntitySchema, }); + +export const overlayElementWithReactionSchema = z.object({ + left: overlayLeftReactionSchema, + right: overlayRightBioEntitySchema, +}); + +export const overlayBioEntitySchema = z.union([ + overlayElementWithBioEntitySchema, + overlayElementWithReactionSchema, +]); diff --git a/src/models/overlayLeftReactionSchema.ts b/src/models/overlayLeftReactionSchema.ts new file mode 100644 index 0000000000000000000000000000000000000000..c47febcff7136233ac38e7d4c41c6eb3d85c2144 --- /dev/null +++ b/src/models/overlayLeftReactionSchema.ts @@ -0,0 +1,34 @@ +import { z } from 'zod'; +import { lineSchema } from './lineSchema'; +import { reactionProduct } from './reactionProduct'; +import { referenceSchema } from './referenceSchema'; + +export const overlayLeftReactionSchema = z.object({ + id: z.number(), + notes: z.string(), + idReaction: z.string(), + name: z.string(), + reversible: z.boolean(), + symbol: z.null(), + abbreviation: z.null(), + formula: z.null(), + mechanicalConfidenceScore: z.null(), + lowerBound: z.null(), + upperBound: z.null(), + subsystem: z.null(), + geneProteinReaction: z.null(), + visibilityLevel: z.string(), + z: z.number(), + synonyms: z.array(z.unknown()), + model: z.number(), + kinetics: z.null(), + line: lineSchema, + processCoordinates: z.null(), + stringType: z.string(), + modifiers: z.array(reactionProduct), + reactants: z.array(reactionProduct), + products: z.array(reactionProduct), + elementId: z.string(), + operators: z.array(z.unknown()), + references: z.array(referenceSchema), +}); diff --git a/src/models/reactionProduct.ts b/src/models/reactionProduct.ts new file mode 100644 index 0000000000000000000000000000000000000000..96905877910e382f1736d595351286f5fa636807 --- /dev/null +++ b/src/models/reactionProduct.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; +import { lineSchema } from './lineSchema'; + +export const reactionProduct = z.object({ + id: z.number(), + line: lineSchema, + stoichiometry: z.null(), + element: z.number(), +}); diff --git a/src/redux/overlayBioEntity/overlayBioEntity.mock.ts b/src/redux/overlayBioEntity/overlayBioEntity.mock.ts index 84fab91d714d1acdbb49aa3275b6b5fe13cc668c..9b093c3eeaecee77b8fc2923f37f8cd3da8c5eec 100644 --- a/src/redux/overlayBioEntity/overlayBioEntity.mock.ts +++ b/src/redux/overlayBioEntity/overlayBioEntity.mock.ts @@ -8,6 +8,7 @@ export const OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK: OverlaysBioEntityState = { export const MOCKED_OVERLAY_BIO_ENTITY_RENDER: OverlayBioEntityRender[] = [ { + type: 'rectangle', id: 1, modelId: 52, width: 30, @@ -21,6 +22,7 @@ export const MOCKED_OVERLAY_BIO_ENTITY_RENDER: OverlayBioEntityRender[] = [ color: null, }, { + type: 'rectangle', id: 2, modelId: 52, width: 30, @@ -34,6 +36,7 @@ export const MOCKED_OVERLAY_BIO_ENTITY_RENDER: OverlayBioEntityRender[] = [ color: null, }, { + type: 'rectangle', id: 3, modelId: 52, width: 40, @@ -46,4 +49,32 @@ export const MOCKED_OVERLAY_BIO_ENTITY_RENDER: OverlayBioEntityRender[] = [ value: null, color: { rgb: -65536, alpha: 0 }, }, + { + type: 'line', + id: 66143, + modelId: 52, + x1: 4462.61826820353, + x2: 4571.99387254902, + y1: 7105.89040426431, + y2: 6979.823529411765, + width: 109.3756043454905, + height: 126.06687485254497, + value: null, + overlayId: 20, + color: null, + }, + { + type: 'line', + id: 66144, + modelId: 52, + x1: 4454.850442288663, + x2: 4463.773636826477, + y1: 7068.434324866321, + y2: 7112.188429617157, + width: 8.923194537814197, + height: 43.75410475083663, + value: null, + overlayId: 20, + color: null, + }, ]; diff --git a/src/redux/overlayBioEntity/overlayBioEntity.utils.ts b/src/redux/overlayBioEntity/overlayBioEntity.utils.ts index 7c92f7a20080e78660bcf391194990c13940451b..6656d725badcafef167e19d63226804077ca4a21 100644 --- a/src/redux/overlayBioEntity/overlayBioEntity.utils.ts +++ b/src/redux/overlayBioEntity/overlayBioEntity.utils.ts @@ -2,6 +2,8 @@ import { ONE } from '@/constants/common'; import { overlayBioEntitySchema } from '@/models/overlayBioEntitySchema'; import { OverlayBioEntityRender } from '@/types/OLrendering'; import { OverlayBioEntity } from '@/types/models'; +import { getOverlayReactionCoordsFromLine } from '@/utils/overlays/getOverlayReactionCoords'; +import { isBioEntity, isReaction } from '@/utils/overlays/overlaysElementsTypeGuards'; import { z } from 'zod'; export const parseOverlayBioEntityToOlRenderingFormat = ( @@ -9,8 +11,16 @@ export const parseOverlayBioEntityToOlRenderingFormat = ( overlayId: number, ): OverlayBioEntityRender[] => data.reduce((acc: OverlayBioEntityRender[], entity: OverlayBioEntity) => { - if (entity.left.x && entity.left.y) { + /** + * The're two types of entities - bioentity and reaction + * Bioentity comes with the single only element + * And reaction comes with many different lines that needs to be merged together + * Every reaction line is a different entity after reduce + */ + + if (isBioEntity(entity)) { acc.push({ + type: 'rectangle', id: entity.left.id, modelId: entity.left.model, x1: entity.left.x, @@ -24,6 +34,31 @@ export const parseOverlayBioEntityToOlRenderingFormat = ( color: entity.right.color, }); } + + if (isReaction(entity)) { + const { products, reactants, modifiers } = entity.left; + const lines = [products, reactants, modifiers].flat().map(element => element.line); + const coords = lines.map(getOverlayReactionCoordsFromLine).flat(); + const elements = coords.map( + ({ x1, x2, y1, y2, id, width, height }): OverlayBioEntityRender => ({ + type: 'line', + id, + modelId: entity.left.model, + x1, + x2, + y1, + y2, + width, + height: Math.abs(height), + value: entity.right.value, + overlayId, + color: entity.right.color, + }), + ); + + acc.push(...elements); + } + return acc; }, []); diff --git a/src/types/OLrendering.ts b/src/types/OLrendering.ts index 11ab030c2ca0dd67874f94da6b29309e81f52e1c..6aee24d703ddc7ab5c39bf21644ad91895886891 100644 --- a/src/types/OLrendering.ts +++ b/src/types/OLrendering.ts @@ -1,5 +1,7 @@ import { Color } from './models'; +export type OverlayBioEntityRenderType = 'line' | 'rectangle'; + export type OverlayBioEntityRender = { id: number; modelId: number; @@ -16,4 +18,15 @@ export type OverlayBioEntityRender = { value: number | null; overlayId: number; color: Color | null; + type: OverlayBioEntityRenderType; }; + +export interface OverlayReactionCoords { + x1: number; + x2: number; + y1: number; + y2: number; + id: number; + height: number; + width: number; +} diff --git a/src/types/models.ts b/src/types/models.ts index e48801f3cd773468ab419c4d233424833c4b3126..437fa8d6655cbdc71ea1d9ec155d965a20fed0fa 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -12,7 +12,8 @@ import { configurationSchema, formatSchema, miriamTypesSchema } from '@/models/c import { disease } from '@/models/disease'; import { drugSchema } from '@/models/drugSchema'; import { elementSearchResult, elementSearchResultType } from '@/models/elementSearchResult'; -import { exportNetworkchema, exportElementsSchema } from '@/models/exportSchema'; +import { exportElementsSchema, exportNetworkchema } from '@/models/exportSchema'; +import { lineSchema } from '@/models/lineSchema'; import { loginSchema } from '@/models/loginSchema'; import { mapBackground } from '@/models/mapBackground'; import { @@ -23,7 +24,13 @@ import { } from '@/models/mapOverlay'; import { mapModelSchema } from '@/models/modelSchema'; import { organism } from '@/models/organism'; -import { overlayBioEntitySchema } from '@/models/overlayBioEntitySchema'; +import { + overlayBioEntitySchema, + overlayElementWithBioEntitySchema, + overlayElementWithReactionSchema, +} from '@/models/overlayBioEntitySchema'; +import { overlayLeftBioEntitySchema } from '@/models/overlayLeftBioEntitySchema'; +import { overlayLeftReactionSchema } from '@/models/overlayLeftReactionSchema'; import { overviewImageLink, overviewImageLinkImage, @@ -67,6 +74,11 @@ export type Configuration = z.infer<typeof configurationSchema>; export type ConfigurationFormatSchema = z.infer<typeof formatSchema>; export type ConfigurationMiramiTypes = z.infer<typeof miriamTypesSchema>; export type OverlayBioEntity = z.infer<typeof overlayBioEntitySchema>; +export type OverlayElementWithReaction = z.infer<typeof overlayElementWithReactionSchema>; +export type OverlayElementWithBioEntity = z.infer<typeof overlayElementWithBioEntitySchema>; +export type OverlayLeftBioEntity = z.infer<typeof overlayLeftBioEntitySchema>; +export type OverlayLeftReaction = z.infer<typeof overlayLeftReactionSchema>; +export type Line = z.infer<typeof lineSchema>; export type CreatedOverlayFile = z.infer<typeof createdOverlayFileSchema>; export type UploadedOverlayFileContent = z.infer<typeof uploadedOverlayFileContentSchema>; export type CreatedOverlay = z.infer<typeof createdOverlaySchema>; diff --git a/src/utils/overlays/getOverlayReactionCoords.test.ts b/src/utils/overlays/getOverlayReactionCoords.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..6d5d2ea1b023364c6ad7d5e06b0a5f062d5cec1c --- /dev/null +++ b/src/utils/overlays/getOverlayReactionCoords.test.ts @@ -0,0 +1,85 @@ +import { OverlayReactionCoords } from '@/types/OLrendering'; +import { Line } from '@/types/models'; +import { getOverlayReactionCoordsFromLine } from './getOverlayReactionCoords'; + +const LINE_DATA_BASE: Line = { + id: 66141, + width: 1, + color: { + alpha: 255, + rgb: -16777216, + }, + z: 0, + segments: [ + { + x1: 4457.375604345491, + y1: 7111.933125147456, + x2: 4462.61826820353, + y2: 7105.89040426431, + }, + ], + startArrow: { + arrowType: 'NONE', + angle: 2.748893571891069, + lineType: 'SOLID', + length: 15, + }, + endArrow: { + arrowType: 'NONE', + angle: 2.748893571891069, + lineType: 'SOLID', + length: 15, + }, + lineType: 'SOLID', +}; + +describe('getOverlayReactionCoords - util', () => { + const cases: [Line, OverlayReactionCoords[]][] = [ + [ + { + ...LINE_DATA_BASE, + segments: [ + { + x1: 10, + y1: 10, + x2: 100, + y2: 100, + }, + ], + }, + [{ height: -90, id: 66141, width: 90, x1: 10, x2: 100, y1: 10, y2: 100 }], + ], + [ + { + ...LINE_DATA_BASE, + segments: [ + { + x1: 10, + y1: 10, + x2: 2000, + y2: 0, + }, + ], + }, + [{ height: 10, id: 66141, width: 1990, x1: 10, x2: 2000, y1: 10, y2: 0 }], + ], + [ + { + ...LINE_DATA_BASE, + segments: [ + { + x1: 0, + y1: 0, + x2: 0, + y2: 0, + }, + ], + }, + [{ height: 0, id: 66141, width: 0, x1: 0, x2: 0, y1: 0, y2: 0 }], + ], + ]; + + it.each(cases)('should return valid result', (line, result) => { + expect(getOverlayReactionCoordsFromLine(line)).toStrictEqual(result); + }); +}); diff --git a/src/utils/overlays/getOverlayReactionCoords.ts b/src/utils/overlays/getOverlayReactionCoords.ts new file mode 100644 index 0000000000000000000000000000000000000000..c607fcde6e01f08334f0a38122646ec145fe17cc --- /dev/null +++ b/src/utils/overlays/getOverlayReactionCoords.ts @@ -0,0 +1,14 @@ +import { OverlayReactionCoords } from '@/types/OLrendering'; +import { Line } from '@/types/models'; + +export const getOverlayReactionCoordsFromLine = (line: Line): OverlayReactionCoords[] => + line.segments.map(segment => { + const { x1, y1, x2, y2 } = segment; + + return { + ...segment, + id: line.id, + width: x2 - x1, + height: y1 - y2, + }; + }); diff --git a/src/utils/overlays/overlaysElementsTypeGuards.test.ts b/src/utils/overlays/overlaysElementsTypeGuards.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..19028e2c88aa16431ee269d267a531998004d189 --- /dev/null +++ b/src/utils/overlays/overlaysElementsTypeGuards.test.ts @@ -0,0 +1,35 @@ +import { + overlayElementWithBioEntityFixture, + overlayElementWithReactionFixture, +} from '@/models/fixtures/overlayBioEntityFixture'; +import { isBioEntity, isReaction } from './overlaysElementsTypeGuards'; + +describe('overlaysElementsTypeGruards - utils', () => { + describe('isReaction', () => { + describe('when is reaction', () => { + it('should return true', () => { + expect(isReaction(overlayElementWithReactionFixture)).toBe(true); + }); + }); + + describe('when is bioentity', () => { + it('should return false', () => { + expect(isReaction(overlayElementWithBioEntityFixture)).toBe(false); + }); + }); + }); + + describe('isBioEntity', () => { + describe('when is reaction', () => { + it('should return false', () => { + expect(isBioEntity(overlayElementWithReactionFixture)).toBe(false); + }); + }); + + describe('when is bioentity', () => { + it('should return true', () => { + expect(isBioEntity(overlayElementWithBioEntityFixture)).toBe(true); + }); + }); + }); +}); diff --git a/src/utils/overlays/overlaysElementsTypeGuards.ts b/src/utils/overlays/overlaysElementsTypeGuards.ts new file mode 100644 index 0000000000000000000000000000000000000000..6997b141f5d627b5d4641ee23c2f7b902f17a1af --- /dev/null +++ b/src/utils/overlays/overlaysElementsTypeGuards.ts @@ -0,0 +1,14 @@ +import { + OverlayBioEntity, + OverlayElementWithBioEntity, + OverlayElementWithReaction, + OverlayLeftBioEntity, + OverlayLeftReaction, +} from '@/types/models'; + +export const isReaction = (e: OverlayBioEntity): e is OverlayElementWithReaction => + (e.left as OverlayLeftReaction).line !== undefined; + +export const isBioEntity = (e: OverlayBioEntity): e is OverlayElementWithBioEntity => + (e.left as OverlayLeftBioEntity).x !== undefined && + (e.left as OverlayLeftBioEntity).y !== undefined;