diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts index 9bc0d80919ec75843004806eb9e57fed2c5bc96c..cf9262f861c4ff75d07427cb83819edff836dc15 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts @@ -19,6 +19,7 @@ import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import CompartmentSquare from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare'; import CompartmentCircle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle'; import { ModelElement } from '@/types/models'; +import CompartmentPathway from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway'; export const useOlMapReactionsLayer = ({ mapInstance, @@ -36,16 +37,48 @@ export const useOlMapReactionsLayer = ({ const shapes = useSelector(bioShapesSelector); const lineTypes = useSelector(lineTypesSelector); - const elements: Array<MapElement | CompartmentCircle | CompartmentSquare> = useMemo(() => { - if (!modelElements || !shapes) return []; + const elements: Array<MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway> = + useMemo(() => { + if (!modelElements || !shapes) return []; - const validElements: Array<MapElement | CompartmentCircle | CompartmentSquare> = []; - modelElements.content.forEach((element: ModelElement) => { - const shape = shapes.find(bioShape => bioShape.sboTerm === element.sboTerm); - if (shape) { - validElements.push( - new MapElement({ - shapes: shape.shapes, + const validElements: Array< + MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway + > = []; + modelElements.content.forEach((element: ModelElement) => { + const shape = shapes.find(bioShape => bioShape.sboTerm === element.sboTerm); + if (shape) { + validElements.push( + new MapElement({ + shapes: shape.shapes, + x: element.x, + y: element.y, + nameX: element.nameX, + nameY: element.nameY, + nameHeight: element.nameHeight, + nameWidth: element.nameWidth, + width: element.width, + height: element.height, + zIndex: element.z, + lineWidth: element.lineWidth, + lineType: element.borderLineType, + fontColor: element.fontColor, + fillColor: element.fillColor, + borderColor: element.borderColor, + nameVerticalAlign: element.nameVerticalAlign as VerticalAlign, + nameHorizontalAlign: element.nameHorizontalAlign as HorizontalAlign, + homodimer: element.homodimer, + activity: element.activity, + text: element.name, + fontSize: element.fontSize, + pointToProjection, + mapInstance, + modifications: element.modificationResidues, + lineTypes, + bioShapes: shapes, + }), + ); + } else if (element.sboTerm === 'SBO:0000290') { + const compartmentProps = { x: element.x, y: element.y, nameX: element.nameX, @@ -55,57 +88,30 @@ export const useOlMapReactionsLayer = ({ width: element.width, height: element.height, zIndex: element.z, - lineWidth: element.lineWidth, - lineType: element.borderLineType, + innerWidth: element.innerWidth, + outerWidth: element.outerWidth, + thickness: element.thickness, fontColor: element.fontColor, fillColor: element.fillColor, borderColor: element.borderColor, nameVerticalAlign: element.nameVerticalAlign as VerticalAlign, nameHorizontalAlign: element.nameHorizontalAlign as HorizontalAlign, - homodimer: element.homodimer, - activity: element.activity, text: element.name, fontSize: element.fontSize, pointToProjection, mapInstance, - modifications: element.modificationResidues, - lineTypes, - bioShapes: shapes, - }), - ); - } else if (element.sboTerm === 'SBO:0000290') { - const compartmentProps = { - x: element.x, - y: element.y, - nameX: element.nameX, - nameY: element.nameY, - nameHeight: element.nameHeight, - nameWidth: element.nameWidth, - width: element.width, - height: element.height, - zIndex: element.z, - innerWidth: element.innerWidth, - outerWidth: element.outerWidth, - thickness: element.thickness, - fontColor: element.fontColor, - fillColor: element.fillColor, - borderColor: element.borderColor, - nameVerticalAlign: element.nameVerticalAlign as VerticalAlign, - nameHorizontalAlign: element.nameHorizontalAlign as HorizontalAlign, - text: element.name, - fontSize: element.fontSize, - pointToProjection, - mapInstance, - }; - if (element.shape === 'OVAL_COMPARTMENT') { - validElements.push(new CompartmentCircle(compartmentProps)); - } else if (element.shape === 'SQUARE_COMPARTMENT') { - validElements.push(new CompartmentSquare(compartmentProps)); + }; + if (element.shape === 'OVAL_COMPARTMENT') { + validElements.push(new CompartmentCircle(compartmentProps)); + } else if (element.shape === 'SQUARE_COMPARTMENT') { + validElements.push(new CompartmentSquare(compartmentProps)); + } else if (element.shape === 'PATHWAY') { + validElements.push(new CompartmentPathway(compartmentProps)); + } } - } - }); - return validElements; - }, [modelElements, shapes, pointToProjection, mapInstance, lineTypes]); + }); + return validElements; + }, [modelElements, shapes, pointToProjection, mapInstance, lineTypes]); const features = useMemo(() => { return elements.map(element => element.multiPolygonFeature); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..8eb98121453483ad0285984b036d836b6174413f --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts @@ -0,0 +1,118 @@ +/* eslint-disable no-magic-numbers */ +import { Feature, Map } from 'ol'; +import { Fill, Style, Text } from 'ol/style'; +import { Polygon, MultiPolygon } from 'ol/geom'; +import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle'; +import getMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getMultiPolygon'; +import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke'; +import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill'; +import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex'; +import View from 'ol/View'; +import { + WHITE_COLOR, + BLACK_COLOR, +} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; +import CompartmentPathway, { + CompartmentPathwayProps, +} from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway'; +import getEllipseCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords'; +import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords'; + +jest.mock('../text/getTextStyle'); +jest.mock('../text/getTextCoords'); +jest.mock('./getMultiPolygon'); +jest.mock('../style/getStroke'); +jest.mock('../coords/getEllipseCoords'); +jest.mock('../style/getFill'); +jest.mock('../style/rgbToHex'); + +describe('MapElement', () => { + let props: CompartmentPathwayProps; + + beforeEach(() => { + const dummyElement = document.createElement('div'); + const mapInstance = new Map({ + target: dummyElement, + view: new View({ + zoom: 5, + minZoom: 3, + maxZoom: 7, + }), + }); + props = { + x: 0, + y: 0, + width: 100, + height: 100, + zIndex: 1, + fillColor: WHITE_COLOR, + borderColor: BLACK_COLOR, + fontColor: BLACK_COLOR, + outerWidth: 2, + text: 'Test Text', + fontSize: 12, + nameX: 10, + nameY: 20, + nameHeight: 30, + nameWidth: 40, + nameVerticalAlign: 'MIDDLE', + nameHorizontalAlign: 'CENTER', + pointToProjection: jest.fn(() => [10, 10]), + mapInstance, + }; + + (getTextStyle as jest.Mock).mockReturnValue( + new Style({ + text: new Text({ + text: props.text, + font: `bold ${props.fontSize}px Arial`, + fill: new Fill({ + color: '#000', + }), + placement: 'point', + textAlign: 'center', + textBaseline: 'middle', + }), + }), + ); + (getTextCoords as jest.Mock).mockReturnValue([10, 10]); + (getMultiPolygon as jest.Mock).mockReturnValue([ + new Polygon([ + [ + [0, 0], + [1, 1], + [2, 2], + ], + ]), + ]); + (getStroke as jest.Mock).mockReturnValue(new Style()); + (getFill as jest.Mock).mockReturnValue(new Style()); + (rgbToHex as jest.Mock).mockReturnValue('#FFFFFF'); + (getEllipseCoords as jest.Mock).mockReturnValue([ + [10, 10], + [20, 20], + [30, 30], + ]); + }); + + it('should initialize with correct default properties', () => { + const multiPolygon = new CompartmentPathway(props); + + expect(multiPolygon.polygons.length).toBe(2); + expect(multiPolygon.multiPolygonFeature).toBeInstanceOf(Feature); + expect(multiPolygon.multiPolygonFeature.getGeometry()).toBeInstanceOf(MultiPolygon); + }); + + it('should apply correct styles to the feature', () => { + const multiPolygon = new CompartmentPathway(props); + const feature = multiPolygon.multiPolygonFeature; + + const style = feature.getStyleFunction()?.call(multiPolygon, feature, 1); + + if (Array.isArray(style)) { + expect(style.length).toBeGreaterThan(0); + } else { + expect(style).toBeInstanceOf(Style); + } + }); +}); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts new file mode 100644 index 0000000000000000000000000000000000000000..3de2f63c8bf7b1d290288c2d1476edf840722b14 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts @@ -0,0 +1,108 @@ +/* eslint-disable no-magic-numbers */ +import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection'; +import { MapInstance } from '@/types/map'; +import { + ColorObject, + HorizontalAlign, + VerticalAlign, +} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types'; +import { + BLACK_COLOR, + WHITE_COLOR, +} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; +import Polygon from 'ol/geom/Polygon'; +import BaseMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon'; +import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle'; + +export type CompartmentPathwayProps = { + x: number; + y: number; + width: number; + height: number; + zIndex: number; + fillColor?: ColorObject; + borderColor?: ColorObject; + fontColor?: ColorObject; + outerWidth?: number; + text?: string; + fontSize?: number; + nameX: number; + nameY: number; + nameHeight: number; + nameWidth: number; + nameVerticalAlign?: VerticalAlign; + nameHorizontalAlign?: HorizontalAlign; + pointToProjection: UsePointToProjectionResult; + mapInstance: MapInstance; +}; + +export default class CompartmentPathway extends BaseMultiPolygon { + outerWidth: number; + + constructor({ + x, + y, + width, + height, + zIndex, + fillColor = WHITE_COLOR, + borderColor = BLACK_COLOR, + fontColor = BLACK_COLOR, + outerWidth = 1, + text = '', + fontSize = 12, + nameX, + nameY, + nameHeight, + nameWidth, + nameVerticalAlign = 'MIDDLE', + nameHorizontalAlign = 'CENTER', + pointToProjection, + mapInstance, + }: CompartmentPathwayProps) { + super({ + x, + y, + width, + height, + zIndex, + text, + fontSize, + nameX, + nameY, + nameWidth, + nameHeight, + fontColor, + nameVerticalAlign, + nameHorizontalAlign, + fillColor, + borderColor, + pointToProjection, + }); + this.outerWidth = outerWidth; + this.createPolygons(); + this.drawText(); + this.drawMultiPolygonFeature(mapInstance); + } + + protected createPolygons(): void { + const compartmentPolygon = new Polygon([ + [ + this.pointToProjection({ x: this.x, y: this.y }), + this.pointToProjection({ x: this.x + this.width, y: this.y }), + this.pointToProjection({ x: this.x + this.width, y: this.y + this.height }), + this.pointToProjection({ x: this.x, y: this.y + this.height }), + ], + ]); + this.styles.push( + getStyle({ + geometry: compartmentPolygon, + borderColor: this.borderColor, + fillColor: { ...this.fillColor, alpha: 9 }, + lineWidth: this.outerWidth, + zIndex: this.zIndex, + }), + ); + this.polygons.push(compartmentPolygon); + } +}