diff --git a/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx b/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx index 6ee65f6b44eeb7c078b7d7fc0250928f933db92f..e5f914e8c2dc8d5947b08817359eba144b923218 100644 --- a/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx +++ b/src/components/Map/Drawer/LayersDrawer/LayersDrawer.component.tsx @@ -23,13 +23,13 @@ export const LayersDrawer = (): JSX.Element => { <div key={layer.details.id} className="flex items-center justify-between border-b p-4"> <h1>{layer.details.name}</h1> <Switch - isChecked={layersVisibilityForCurrentModel[layer.details.layerId]} + isChecked={layersVisibilityForCurrentModel[layer.details.id]} onToggle={value => dispatch( setLayerVisibility({ modelId: currentModelId, visible: value, - layerId: layer.details.layerId, + layerId: layer.details.id, }), ) } diff --git a/src/components/Map/MapViewer/MapViewer.component.tsx b/src/components/Map/MapViewer/MapViewer.component.tsx index e4c857412058ee83bd83dabdfbc873628505e40c..662384c9bd6b215ea3277fd94c0568eac7dc081d 100644 --- a/src/components/Map/MapViewer/MapViewer.component.tsx +++ b/src/components/Map/MapViewer/MapViewer.component.tsx @@ -9,7 +9,7 @@ export const MapViewer = (): JSX.Element => { <div ref={mapRef} role={MAP_VIEWER_ROLE} - className="absolute left-[88px] top-[104px] h-[calc(100%-104px)] w-[calc(100%-88px)] bg-[#e4e2de]" + className="absolute left-[88px] top-[104px] h-[calc(100%-104px)] w-[calc(100%-88px)] bg-white" /> ); }; diff --git a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts index 79fab6da0b00d8ecbf3ea959d8fc8a964e105f7f..5bc52a7fd486077744fe048effab865d59c6639e 100644 --- a/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts +++ b/src/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants.ts @@ -2,7 +2,7 @@ import { Color, ShapeRelAbs, ShapeRelAbsBezierPoint } from '@/types/models'; export const VECTOR_MAP_LAYER_TYPE = 'vectorMapLayer'; -export const COMPLEX_SBO_TERM = 'SBO:0000253'; +export const COMPLEX_SBO_TERMS = ['SBO:0000253', 'SBO:0000297', 'SBO:0000289']; export const WHITE_COLOR: Color = { alpha: 255, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts index b37cffdaf794171804473a8a0dc3c438c5350b3a..f0dfaa9fc3ccd4d993bfe3d174ad6a4525689842 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers.ts @@ -55,7 +55,7 @@ export const useOlMapAdditionalLayers = ( ovals: layer.ovals, lines: layer.lines, visible: layer.details.visible, - layerId: layer.details.layerId, + layerId: layer.details.id, lineTypes, arrowTypes, mapInstance, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts index 2e5bbf9969a67b00336a3c48105ec32636cdcc00..2f5b97ba2fd9449bff6952895cc0ec603b81bf01 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements.ts @@ -54,6 +54,7 @@ export default function processModelElements( if (element.sboTerm === 'SBO:0000290') { const compartmentProps = { id: element.id, + sboTerm: element.sboTerm, complexId: element.complex, compartmentId: element.compartment, x: element.x, 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 6f2aafa1406a548c7b9e4a8199b30e783c552015..86787f117e8e0c717e13eca665b2517e47da35ac 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts @@ -178,7 +178,7 @@ export const useOlMapReactionsLayer = ({ vectorSource, mapInstance, }); - return reactionObject.features; + return [reactionObject.lineFeature, ...reactionObject.reactionFeatures]; }); }, [ reactionsForCurrentModel, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.test.ts index fed682be2c760b0f5b505436cdae182cd2976ca4..9ec7e61450fa2c2176760f600ba4bca508566bef 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.test.ts @@ -82,11 +82,4 @@ describe('useOlMapLayers - util', () => { expect(result[0]).toBeInstanceOf(VectorLayer); expect(result[0].getSourceState()).toBe('ready'); }); - - it('should return valid VectorLayer instance [2]', () => { - const result = getRenderedHookResults(); - - expect(result[1]).toBeInstanceOf(VectorLayer); - expect(result[1].getSourceState()).toBe('ready'); - }); }); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts index cebab4e092508f7d15bbeaf8a649166db7dfbe02..822b54ddd79e39d5d43c58f19464c070f8d6222d 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapVectorLayers.ts @@ -1,6 +1,5 @@ /* eslint-disable no-magic-numbers */ import { MapInstance } from '@/types/map'; -import { useOlMapWhiteCardLayer } from '@/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer'; import { useOlMapAdditionalLayers } from '@/components/Map/MapViewer/MapViewerVector/utils/config/additionalLayers/useOlMapAdditionalLayers'; import { useMemo } from 'react'; import { useOlMapReactionsLayer } from './reactionsLayer/useOlMapReactionsLayer'; @@ -12,10 +11,9 @@ interface UseOlMapLayersInput { export const useOlMapVectorLayers = ({ mapInstance }: UseOlMapLayersInput): MapConfig['layers'] => { const reactionsLayer = useOlMapReactionsLayer({ mapInstance }); - const whiteCardLayer = useOlMapWhiteCardLayer(); const additionalLayers = useOlMapAdditionalLayers(mapInstance); return useMemo(() => { - return [whiteCardLayer, reactionsLayer, ...additionalLayers]; - }, [whiteCardLayer, reactionsLayer, additionalLayers]); + return [reactionsLayer, ...additionalLayers]; + }, [reactionsLayer, additionalLayers]); }; diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer.test.ts deleted file mode 100644 index 394abc0c4a86767c4c8e2aee6d74e680fc965fae..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { MAP_DATA_INITIAL_STATE, OPENED_MAPS_INITIAL_STATE } from '@/redux/map/map.constants'; -import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; -import { renderHook } from '@testing-library/react'; -import BaseLayer from 'ol/layer/Base'; -import VectorLayer from 'ol/layer/Vector'; -import React from 'react'; -import MapBackgroundsEnum from '@/redux/map/map.enums'; -import { useOlMapWhiteCardLayer } from './useOlMapWhiteCardLayer'; - -const useRefValue = { - current: null, -}; - -Object.defineProperty(useRefValue, 'current', { - get: jest.fn(() => ({ - innerHTML: '', - appendChild: jest.fn(), - addEventListener: jest.fn(), - getRootNode: jest.fn(), - })), - set: jest.fn(() => ({ - innerHTML: '', - appendChild: jest.fn(), - addEventListener: jest.fn(), - getRootNode: jest.fn(), - })), -}); - -jest.spyOn(React, 'useRef').mockReturnValue(useRefValue); - -describe('useOlMapWhiteCardLayer - util', () => { - const getRenderedHookResults = (): BaseLayer => { - const { Wrapper } = getReduxWrapperWithStore({ - map: { - data: { - ...MAP_DATA_INITIAL_STATE, - size: { - width: 256, - height: 256, - tileSize: 256, - minZoom: 1, - maxZoom: 1, - }, - position: { - initial: { - x: 256, - y: 256, - }, - last: { - x: 256, - y: 256, - }, - }, - }, - loading: 'idle', - error: { - name: '', - message: '', - }, - openedMaps: OPENED_MAPS_INITIAL_STATE, - backgroundType: MapBackgroundsEnum.SEMANTIC, - }, - }); - - const { result } = renderHook(() => useOlMapWhiteCardLayer(), { - wrapper: Wrapper, - }); - - return result.current; - }; - - it('should return valid TileLayer instance', () => { - const result = getRenderedHookResults(); - - expect(result).toBeInstanceOf(VectorLayer); - expect(result.getSourceState()).toBe('ready'); - }); -}); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer.ts deleted file mode 100644 index f54b8d744acbe1b51c1c092749a83c02b6040997..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/useOlMapWhiteCardLayer.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Feature } from 'ol'; -import VectorLayer from 'ol/layer/Vector'; -import VectorSource from 'ol/source/Vector'; -import { useMemo } from 'react'; -import Polygon from 'ol/geom/Polygon'; -import { useSelector } from 'react-redux'; -import { mapDataSizeSelector } from '@/redux/map/map.selectors'; -import { usePointToProjection } from '@/utils/map/usePointToProjection'; -import Style from 'ol/style/Style'; -import { Fill } from 'ol/style'; - -export const useOlMapWhiteCardLayer = (): VectorLayer<VectorSource<Feature<Polygon>>> => { - const mapSize = useSelector(mapDataSizeSelector); - const pointToProjection = usePointToProjection(); - - const rectangle = useMemo(() => { - return new Polygon([ - [ - pointToProjection({ x: 0, y: 0 }), - pointToProjection({ x: mapSize.width, y: 0 }), - pointToProjection({ x: mapSize.width, y: mapSize.height }), - pointToProjection({ x: 0, y: mapSize.height }), - pointToProjection({ x: 0, y: 0 }), - ], - ]); - }, [mapSize.height, mapSize.width, pointToProjection]); - - const rectangleFeature = useMemo(() => { - return new Feature(rectangle); - }, [rectangle]); - - const vectorSource = useMemo(() => { - return new VectorSource({ - features: [rectangleFeature], - }); - }, [rectangleFeature]); - - return useMemo( - () => - new VectorLayer({ - source: vectorSource, - style: new Style({ - fill: new Fill({ - color: '#fff', - }), - }), - }), - [vectorSource], - ); -}; diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBezierCurve.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBezierCurve.ts index 814e7aac4dcab68cf9cb5a0e04b0763eaf97f0bd..b086befa4bf2a29109185a388b2d8c9442ab3513 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBezierCurve.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getBezierCurve.ts @@ -6,7 +6,7 @@ export default function getBezierCurve({ p1, p2, p3, - numPoints = 50, + numPoints = 3, }: { p0: Coordinate; p1: Coordinate; diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords.ts index aca8a8927849ca830af1a0eb77dbf674346a1f10..c617ffafb8bc9e0fd2205b5f3a9b9a85d446df43 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getEllipseCoords.ts @@ -13,7 +13,7 @@ export default function getEllipseCoords({ height, width, pointToProjection, - points = 20, + points = 30, }: { x: number; y: number; diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getLineSegments.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getLineSegments.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..ec6d5a70dd0c170083be7e1766888ce3dad41c7c --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getLineSegments.test.ts @@ -0,0 +1,46 @@ +/* eslint-disable no-magic-numbers */ +import { Line } from '@/types/models'; +import { BLACK_COLOR } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; +import getLineSegments from './getLineSegments'; + +describe('getLineSegments', () => { + it('should return flattened array of projected coordinates for all line segments', () => { + const pointToProjection = jest.fn(({ x, y }) => [x * 10, y * 10]); + + const line = { + id: 1, + width: 1, + color: BLACK_COLOR, + z: 1, + startArrow: { + arrowType: 'NONE', + angle: 2.748893571891069, + lineType: 'SOLID', + length: 15.0, + }, + endArrow: { + arrowType: 'NONE', + angle: 2.748893571891069, + lineType: 'SOLID', + length: 15.0, + }, + segments: [ + { x1: 1, y1: 2, x2: 3, y2: 4 }, + { x1: 3, y1: 4, x2: 5, y2: 6 }, + ], + } as Line; + + const result = getLineSegments(line, pointToProjection); + + expect(pointToProjection).toHaveBeenCalledTimes(3); + expect(pointToProjection).toHaveBeenNthCalledWith(1, { x: 1, y: 2 }); + expect(pointToProjection).toHaveBeenNthCalledWith(2, { x: 3, y: 4 }); + expect(pointToProjection).toHaveBeenNthCalledWith(3, { x: 5, y: 6 }); + + expect(result).toEqual([ + [10, 20], + [30, 40], + [50, 60], + ]); + }); +}); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getLineSegments.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getLineSegments.ts new file mode 100644 index 0000000000000000000000000000000000000000..c7afcc53417227ddf9a52b7468f78721d03d7340 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getLineSegments.ts @@ -0,0 +1,21 @@ +/* eslint-disable no-magic-numbers */ +import { Coordinate } from 'ol/coordinate'; +import { Line } from '@/types/models'; +import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection'; + +export default function getLineSegments( + line: Line, + pointToProjection: UsePointToProjectionResult, +): Array<Coordinate> { + return line.segments + .map((segment, index) => { + if (index === 0) { + return [ + pointToProjection({ x: segment.x1, y: segment.y1 }), + pointToProjection({ x: segment.x2, y: segment.y2 }), + ]; + } + return [pointToProjection({ x: segment.x2, y: segment.y2 })]; + }) + .flat(); +} diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords.ts index 69af893aee3cfa3959d6ab238e16e7877da9d1a2..9f68ed47511d946efe7f178e3c7b152c1ea3494d 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getPolygonCoords.ts @@ -47,6 +47,6 @@ export default function getPolygonCoords({ const { p1, p2, p3 } = getCurveCoords({ x, y, point, height, width, pointToProjection }); const p0 = lastPoint; lastPoint = p3; - return getBezierCurve({ p0, p1, p2, p3, numPoints: 20 }); + return getBezierCurve({ p0, p1, p2, p3, numPoints: 3 }); }); } diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts index 351fef81ad1b0c890e73c4de6a9d001d3d801fa1..c13c61f4b2ed27bbb2ccbcf39df119ee957b4eab 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts @@ -1,12 +1,11 @@ /* eslint-disable no-magic-numbers */ import Polygon from 'ol/geom/Polygon'; -import { Style } from 'ol/style'; +import { Stroke, Style } from 'ol/style'; import Feature, { FeatureLike } from 'ol/Feature'; import { MultiPolygon } from 'ol/geom'; import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection'; import { HorizontalAlign, - ScaleFunction, VerticalAlign, } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types'; import { MapInstance } from '@/types/map'; @@ -15,7 +14,7 @@ import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shape import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextCoords'; import { Color } from '@/types/models'; import { - COMPLEX_SBO_TERM, + COMPLEX_SBO_TERMS, MAP_ELEMENT_TYPES, } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; import VectorSource from 'ol/source/Vector'; @@ -27,7 +26,7 @@ import handleSemanticView from '@/components/Map/MapViewer/MapViewerVector/utils export interface BaseMapElementProps { type: string; - sboTerm?: string; + sboTerm: string; id: number; complexId?: number | null; compartmentId: number | null; @@ -52,12 +51,13 @@ export interface BaseMapElementProps { vectorSource: VectorSource; mapBackgroundType: number; mapSize: MapSize; + mapInstance: MapInstance; } export default abstract class BaseMultiPolygon { type: string; - sboTerm: string | undefined; + sboTerm: string; id: number; @@ -118,6 +118,8 @@ export default abstract class BaseMultiPolygon { [number, number, number, number] >(); + minResolution: number; + constructor({ type, sboTerm, @@ -145,6 +147,7 @@ export default abstract class BaseMultiPolygon { vectorSource, mapBackgroundType, mapSize, + mapInstance, }: BaseMapElementProps) { this.type = type; this.sboTerm = sboTerm; @@ -172,6 +175,9 @@ export default abstract class BaseMultiPolygon { this.vectorSource = vectorSource; this.mapBackgroundType = mapBackgroundType; this.mapSize = mapSize; + + const maxZoom = mapInstance?.getView().get('originalMaxZoom'); + this.minResolution = mapInstance?.getView().getResolutionForZoom(maxZoom) || 1; } protected abstract createPolygons(): void; @@ -200,6 +206,7 @@ export default abstract class BaseMultiPolygon { horizontalAlign: this.nameHorizontalAlign, }); textStyle.setGeometry(textPolygon); + textPolygon.set('style', textStyle); this.styles.push(textStyle); this.polygons.push(textPolygon); } @@ -209,19 +216,6 @@ export default abstract class BaseMultiPolygon { this.feature = new Feature({ geometry: new MultiPolygon(this.polygons), zIndex: this.zIndex, - getScale: ((): ScaleFunction => { - const maxZoom = mapInstance?.getView().get('originalMaxZoom'); - const minResolution = maxZoom - ? mapInstance?.getView().getResolutionForZoom(maxZoom) - : undefined; - - return (resolution: number): number => { - if (minResolution) { - return minResolution / resolution; - } - return 1; - }; - })(), getMapExtent: (resolution: number): [number, number, number, number] | undefined => { if (this.mapExtentCache.has(resolution)) { return this.mapExtentCache.get(resolution); @@ -262,14 +256,15 @@ export default abstract class BaseMultiPolygon { return undefined; } const styles: Array<Style> = []; - const getScale = feature.get('getScale'); - let scale = 1; + const scale = this.minResolution / resolution; let cover = false; let largestExtent: Extent | null; - if (getScale instanceof Function) { - scale = getScale(resolution); + if (this.complexId && !COMPLEX_SBO_TERMS.includes(this.sboTerm) && scale < 0.215) { + feature.set('hidden', true); + return []; } + feature.set('hidden', false); let hide = false; if (this.mapBackgroundType === MapBackgroundsEnum.SEMANTIC) { @@ -292,9 +287,9 @@ export default abstract class BaseMultiPolygon { let type: string; let fontSize: number; - let lineWidth: number; let text: string; let coverStyle: Style | undefined; + let strokeStyle: Stroke | undefined; this.styles.forEach(style => { const styleGeometry = style.getGeometry(); @@ -302,8 +297,8 @@ export default abstract class BaseMultiPolygon { type = styleGeometry.get('type'); text = styleGeometry.get('text'); fontSize = styleGeometry.get('fontSize') || 10; - lineWidth = styleGeometry.get('lineWidth'); coverStyle = styleGeometry.get('coverStyle'); + strokeStyle = styleGeometry.get('strokeStyle'); } if (cover) { @@ -329,30 +324,36 @@ export default abstract class BaseMultiPolygon { return; } - const clonedStyle = style.clone(); - const textStyle = clonedStyle.getText(); - const strokeStyle = clonedStyle.getStroke(); + const textStyle = style.getText(); if (type === 'text' && textStyle) { textStyle.setScale(scale); textStyle.setText(text); } - if (strokeStyle && lineWidth) { + if (strokeStyle) { + const lineWidth = strokeStyle.getWidth() || 1; if ( !this.overlaysVisible && - lineWidth * scale < 0.08 && - this.sboTerm !== COMPLEX_SBO_TERM + scale < 0.18 && + !COMPLEX_SBO_TERMS.includes(this.sboTerm) && + this.type !== MAP_ELEMENT_TYPES.COMPARTMENT ) { - clonedStyle.setStroke(null); + style.setStroke(null); } else { - strokeStyle.setWidth(lineWidth * scale); const lineDash = strokeStyle.getLineDash(); + let newLineDash: Array<number> = []; if (lineDash) { - const newLineDash = lineDash.map(width => width * scale); - strokeStyle.setLineDash(newLineDash); + newLineDash = lineDash.map(width => width * scale); } + const newStrokeStyle = new Stroke({ + color: strokeStyle.getColor(), + width: lineWidth * scale, + lineDash: newLineDash, + }); + + style.setStroke(newStrokeStyle); } } - styles.push(clonedStyle); + styles.push(style); }); return styles; diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts index b851e10ae7959d90a2117167e6343cc35d664570..8906e75dab9219c5aa4b370254c564f5dc3eedc5 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts @@ -7,7 +7,7 @@ import { import BaseMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon'; import { Coordinate } from 'ol/coordinate'; import Polygon from 'ol/geom/Polygon'; -import { Style } from 'ol/style'; +import { Stroke, Style } from 'ol/style'; import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill'; import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex'; import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke'; @@ -24,6 +24,7 @@ export interface CompartmentProps { id: number; complexId?: number | null; compartmentId: number | null; + sboTerm: string; x: number; y: number; width: number; @@ -68,6 +69,7 @@ export default abstract class Compartment extends BaseMultiPolygon { id, complexId, compartmentId, + sboTerm, x, y, width, @@ -99,6 +101,7 @@ export default abstract class Compartment extends BaseMultiPolygon { id, complexId, compartmentId, + sboTerm, x, y, width, @@ -120,6 +123,7 @@ export default abstract class Compartment extends BaseMultiPolygon { vectorSource, mapBackgroundType, mapSize, + mapInstance, }); this.outerWidth = outerWidth; this.innerWidth = innerWidth; @@ -161,13 +165,17 @@ export default abstract class Compartment extends BaseMultiPolygon { const outerPolygon = new Polygon([this.outerCoords]); outerPolygon.set('type', MAP_ELEMENT_TYPES.COMPARTMENT); - outerPolygon.set('lineWidth', this.outerWidth); + let outerPolygonStroke: Stroke | undefined; + if (this.overlaysVisible) { + outerPolygonStroke = getStroke({ width: this.outerWidth }); + } else { + outerPolygonStroke = getStroke({ color: rgbToHex(this.borderColor), width: this.outerWidth }); + } + outerPolygon.set('strokeStyle', outerPolygonStroke); this.styles.push( new Style({ geometry: outerPolygon, - stroke: this.overlaysVisible - ? getStroke({ width: this.outerWidth }) - : getStroke({ color: rgbToHex(this.borderColor), width: this.outerWidth }), + stroke: outerPolygonStroke, zIndex: this.zIndex, }), ); @@ -175,13 +183,17 @@ export default abstract class Compartment extends BaseMultiPolygon { const innerPolygon = new Polygon([this.innerCoords]); innerPolygon.set('type', MAP_ELEMENT_TYPES.COMPARTMENT); - innerPolygon.set('lineWidth', this.innerWidth); + let innerPolygonStroke: Stroke | undefined; + if (this.overlaysVisible) { + innerPolygonStroke = getStroke({ width: this.innerWidth }); + } else { + innerPolygonStroke = getStroke({ color: rgbToHex(this.borderColor), width: this.innerWidth }); + } + innerPolygon.set('strokeStyle', innerPolygonStroke); this.styles.push( new Style({ geometry: innerPolygon, - stroke: this.overlaysVisible - ? getStroke({ width: this.innerWidth }) - : getStroke({ color: rgbToHex(this.borderColor), width: this.innerWidth }), + stroke: innerPolygonStroke, fill: this.overlaysVisible ? getFill({ color: rgbToHex(TRANSPARENT_COLOR) }) : getFill({ color: rgbToHex({ ...this.fillColor, alpha: 9 }) }), diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts index 876dd762cb714b5cefe1d6d8c1ce1599c8b46a54..7c5956c6be2eeca72fa7078138faf28b4a330367 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts @@ -46,6 +46,7 @@ describe('CompartmentCircle', () => { id: 1, complexId: null, compartmentId: null, + sboTerm: 'SBO:0000253', x: 0, y: 0, width: 100, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts index 3dd381839b3031d3ab651eba15a991983c6631a9..94a65df80c0218061ea17e28e2af846f3842d65a 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts @@ -21,6 +21,7 @@ export type CompartmentCircleProps = { id: number; complexId?: number | null; compartmentId: number | null; + sboTerm: string; x: number; y: number; width: number; @@ -53,6 +54,7 @@ export default class CompartmentCircle extends Compartment { id, complexId, compartmentId, + sboTerm, x, y, width, @@ -83,6 +85,7 @@ export default class CompartmentCircle extends Compartment { id, complexId, compartmentId, + sboTerm, x, y, width, @@ -119,7 +122,7 @@ export default class CompartmentCircle extends Compartment { radius: COMPARTMENT_CIRCLE_RADIUS, height: this.height, width: this.width, - points: 40, + points: 36, pointToProjection: this.pointToProjection, }); this.innerCoords = getEllipseCoords({ @@ -129,7 +132,7 @@ export default class CompartmentCircle extends Compartment { radius: COMPARTMENT_CIRCLE_RADIUS, height: this.height - 2 * this.thickness, width: this.width - 2 * this.thickness, - points: 40, + points: 20, pointToProjection: this.pointToProjection, }); } 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 index 5e1df8106f1107df13050f142c98190e7ea55248..00c1931b89ff0f4a291a1f530ece23c2964a5ed4 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts @@ -46,6 +46,7 @@ describe('CompartmentPathway', () => { id: 1, complexId: null, compartmentId: null, + sboTerm: 'SBO:0000253', x: 0, y: 0, width: 100, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts index d51153e65ff94ef08906849a27a6706df801f3b0..e758a681ec3addb34553e58e79ed8085b72aa6e2 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts @@ -20,11 +20,13 @@ import { Style } from 'ol/style'; import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill'; import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex'; import { MapSize } from '@/redux/map/map.types'; +import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke'; export type CompartmentPathwayProps = { id: number; complexId?: number | null; compartmentId: number | null; + sboTerm: string; x: number; y: number; width: number; @@ -59,6 +61,7 @@ export default class CompartmentPathway extends BaseMultiPolygon { id, complexId, compartmentId, + sboTerm, x, y, width, @@ -88,6 +91,7 @@ export default class CompartmentPathway extends BaseMultiPolygon { id, complexId, compartmentId, + sboTerm, x, y, width, @@ -109,6 +113,7 @@ export default class CompartmentPathway extends BaseMultiPolygon { vectorSource, mapBackgroundType, mapSize, + mapInstance, }); this.outerWidth = outerWidth; this.overlaysVisible = overlaysVisible; @@ -128,12 +133,18 @@ export default class CompartmentPathway extends BaseMultiPolygon { ], ]); compartmentPolygon.set('type', MAP_ELEMENT_TYPES.COMPARTMENT); - compartmentPolygon.set('lineWidth', this.outerWidth); const coverStyle = new Style({ geometry: compartmentPolygon, fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 255 }) }), }); compartmentPolygon.set('coverStyle', coverStyle); + compartmentPolygon.set( + 'strokeStyle', + getStroke({ + color: rgbToHex(this.borderColor), + width: this.outerWidth, + }), + ); this.styles.push( getStyle({ geometry: compartmentPolygon, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts index c7721148a489219765bcb0936193a8514fb484fd..53fff2ff5581f7accf491dff4c354b4005a37bfd 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts @@ -44,6 +44,7 @@ describe('CompartmentSquare', () => { id: 1, complexId: null, compartmentId: null, + sboTerm: 'SBO:0000253', x: 0, y: 0, width: 100, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts index b4905d61d4188aaea63fbb78ea20f314b581f7d0..8f033df3e9aba3d016d428c63752f3ea4a345f0b 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts @@ -20,6 +20,7 @@ export type CompartmentSquareProps = { id: number; complexId?: number | null; compartmentId: number | null; + sboTerm: string; x: number; y: number; width: number; @@ -52,6 +53,7 @@ export default class CompartmentSquare extends Compartment { id, complexId, compartmentId, + sboTerm, x, y, width, @@ -82,6 +84,7 @@ export default class CompartmentSquare extends Compartment { id, complexId, compartmentId, + sboTerm, x, y, width, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts index 6ef7a733034a74132b8562a6993aea0535c08b13..77ab7ebc56e566cf25e5c2281d60f05d1a2f7d68 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts @@ -163,6 +163,7 @@ export default class MapElement extends BaseMultiPolygon { overlaysVisible, mapBackgroundType, mapSize, + mapInstance, }); this.shapes = shapes; this.lineWidth = lineWidth; @@ -221,13 +222,14 @@ export default class MapElement extends BaseMultiPolygon { }); modificationPolygon.set('type', MAP_ELEMENT_TYPES.MODIFICATION); modificationPolygon.set('fontSize', modification.fontSize); - modificationPolygon.set('lineWidth', 1); + const modificationStrokeStyle = getStroke({ color: rgbToHex(modification.borderColor) }); const modificationStyle = new Style({ geometry: modificationPolygon, - stroke: getStroke({ color: rgbToHex(modification.borderColor) }), + stroke: modificationStrokeStyle, fill: getFill({ color: rgbToHex(modification.fillColor) }), zIndex: modification.z, }); + modificationPolygon.set('strokeStyle', modificationStrokeStyle); this.polygons.push(modificationPolygon); this.styles.push(modificationStyle); }); @@ -276,13 +278,18 @@ export default class MapElement extends BaseMultiPolygon { pointToProjection: this.pointToProjection, }); activityBorderPolygon.set('type', MAP_ELEMENT_TYPES.ACTIVITY_BORDER); - activityBorderPolygon.set('lineWidth', 1); const activityBorderStyle = getStyle({ geometry: activityBorderPolygon, fillColor: TRANSPARENT_COLOR, lineDash: [3, 5], zIndex: this.zIndex, }); + activityBorderPolygon.set( + 'strokeStyle', + getStroke({ + lineDash: [3, 5], + }), + ); this.polygons.push(activityBorderPolygon); this.styles.push(activityBorderStyle); }); @@ -299,7 +306,6 @@ export default class MapElement extends BaseMultiPolygon { pointToProjection: this.pointToProjection, }); elementPolygon.set('type', MAP_ELEMENT_TYPES.ENTITY); - elementPolygon.set('lineWidth', this.lineWidth); const elementStyle = getStyle({ geometry: elementPolygon, borderColor: this.borderColor, @@ -308,6 +314,14 @@ export default class MapElement extends BaseMultiPolygon { lineDash: this.lineDash, zIndex: this.zIndex, }); + elementPolygon.set( + 'strokeStyle', + getStroke({ + color: rgbToHex(this.borderColor), + width: this.lineWidth, + lineDash: this.lineDash, + }), + ); this.polygons.push(elementPolygon); this.styles.push(elementStyle); }); @@ -335,13 +349,18 @@ export default class MapElement extends BaseMultiPolygon { ], ]); polygon.set('type', MAP_ELEMENT_TYPES.OVERLAY); - polygon.set('lineWidth', 1); const style = getStyle({ geometry: polygon, borderColor: color, fillColor: color, zIndex: this.zIndex, }); + polygon.set( + 'strokeStyle', + getStroke({ + color, + }), + ); this.polygons.push(polygon); this.styles.push(style); }); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getArrowFeature.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getArrowFeature.ts index 02e0ab19de5d93cb2c793d89631e86370370dcd0..c8f46d9d1f37e57a44f43889412f16ff448c114b 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getArrowFeature.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getArrowFeature.ts @@ -9,6 +9,8 @@ import getShapePolygon from '@/components/Map/MapViewer/MapViewerVector/utils/sh import Polygon from 'ol/geom/Polygon'; import { WHITE_COLOR } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; import { ArrowTypeDict } from '@/redux/shapes/shapes.types'; +import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke'; +import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex'; export default function getArrowFeature({ arrowTypes, @@ -53,6 +55,13 @@ export default function getArrowFeature({ fillColor: shape.fill === false ? WHITE_COLOR : color, lineWidth, }); + arrowPolygon.set( + 'strokeStyle', + getStroke({ + color: rgbToHex(color), + width: lineWidth, + }), + ); arrowPolygon.rotate(rotation, pointToProjection({ x, y })); arrowStyles.push(style); arrowPolygons.push(arrowPolygon); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts index 96829031476236840553f6d7da063b086dc60eee..46baee4385088e620a5170c6bde5521d95ee497f 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.test.ts @@ -117,7 +117,7 @@ describe('Layer', () => { }, ], visible: true, - layerId: '23', + layerId: 23, pointToProjection: jest.fn(point => [point.x, point.y]), mapInstance, lineTypes: {}, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts index ec4cab79d3fe8b6383b9bb32abb341d4812cb873..29f17e00af39fb20d135bf1784b5abeb03a83737 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/layer/Layer.ts @@ -24,6 +24,7 @@ import { TRANSPARENT_COLOR, } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; import getScaledElementStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle'; +import { Stroke } from 'ol/style'; export interface LayerProps { texts: Array<LayerText>; @@ -31,7 +32,7 @@ export interface LayerProps { ovals: Array<LayerOval>; lines: Array<LayerLine>; visible: boolean; - layerId: string; + layerId: number; lineTypes: LineTypeDict; arrowTypes: ArrowTypeDict; mapInstance: MapInstance; @@ -171,7 +172,7 @@ export default class Layer { height: oval.height, width: oval.width, pointToProjection: this.pointToProjection, - points: 36, + points: 20, }); const polygon = new Polygon([coords]); const polygonStyle = getStyle({ @@ -264,7 +265,6 @@ export default class Layer { }); if (endArrowFeature) { endArrowFeature.set('elementType', LAYER_ELEMENT_TYPES.ARROW); - endArrowFeature.set('lineWidth', line.width); endArrowFeature.setStyle(this.getStyle.bind(this)); arrowsFeatures.push(endArrowFeature); } @@ -302,22 +302,27 @@ export default class Layer { } const scale = minResolution / resolution; - const lineWidth = feature.get('lineWidth'); + let strokeStyle: Stroke | undefined; const type = feature.get('elementType'); if (type === LAYER_ELEMENT_TYPES.ARROW && scale <= 0.08) { return []; } + const stylesToProcess: Array<Style> = []; if (style instanceof Style) { - styles.push(getScaledElementStyle(style, lineWidth, scale)); + stylesToProcess.push(style); } else if (Array.isArray(style)) { - style.forEach(singleStyle => { - if (singleStyle instanceof Style) { - styles.push(getScaledElementStyle(singleStyle, lineWidth, scale)); - } - }); + stylesToProcess.push(...style); } + stylesToProcess.forEach(singleStyle => { + const styleGeometry = singleStyle.getGeometry(); + if (styleGeometry instanceof Polygon || styleGeometry instanceof LineString) { + strokeStyle = styleGeometry.get('strokeStyle'); + } + styles.push(getScaledElementStyle(singleStyle, strokeStyle, scale)); + }); + return styles; } } diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts index 70c5b3e465362eb34f472320dba66678c61e4925..0e6dce2418320aa3460a1067c3e52c82bdad2c8f 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts @@ -51,7 +51,8 @@ describe('Layer', () => { it('should initialize a Reaction class', () => { const reaction = new Reaction(props); - expect(reaction.features.length).toBe(12); - expect(reaction.features).toBeInstanceOf(Array<Feature>); + expect(reaction.reactionFeatures.length).toBe(3); + expect(reaction.reactionFeatures).toBeInstanceOf(Array<Feature>); + expect(reaction.lineFeature).toBeInstanceOf(Feature); }); }); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts index 433108642e9a9a08fa5044005c8b4cbb20c07f30..afeeeeab62cb3c6d44a93abdb68239640aca7aec 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts @@ -2,11 +2,8 @@ import { Line, Operator, ReactionProduct, Shape } from '@/types/models'; import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection'; import { Feature } from 'ol'; -import { Circle, LineString, MultiPolygon } from 'ol/geom'; -import getRotation from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation'; +import { Circle, LineString, MultiLineString, MultiPolygon } from 'ol/geom'; import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStyle'; -import getArrowFeature from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getArrowFeature'; -import getShapePolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getShapePolygon'; import Polygon from 'ol/geom/Polygon'; import Style from 'ol/style/Style'; import { @@ -15,12 +12,18 @@ import { } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; import { FeatureLike } from 'ol/Feature'; import { MapInstance } from '@/types/map'; -import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle'; import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex'; import { ArrowTypeDict, LineTypeDict } from '@/redux/shapes/shapes.types'; import { FEATURE_TYPE } from '@/constants/features'; import getScaledElementStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle'; import VectorSource from 'ol/source/Vector'; +import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke'; +import { Stroke } from 'ol/style'; +import getLineSegments from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getLineSegments'; +import getRotation from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/coords/getRotation'; +import getArrowFeature from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getArrowFeature'; +import getShapePolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/getShapePolygon'; +import getTextStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/text/getTextStyle'; export interface ReactionProps { id: number; @@ -63,9 +66,13 @@ export default class Reaction { vectorSource: VectorSource; - mapInstance: MapInstance; + lineFeature: Feature<MultiLineString> = new Feature(); + + reactionFeatures: Array<Feature<MultiPolygon> | Feature<Circle>> = []; - features: Array<Feature> = []; + lineStyles: Array<Style> = []; + + minResolution: number; constructor({ id, @@ -94,56 +101,51 @@ export default class Reaction { this.shapes = shapes; this.pointToProjection = pointToProjection; this.vectorSource = vectorSource; - this.mapInstance = mapInstance; + + const maxZoom = mapInstance?.getView().get('originalMaxZoom'); + this.minResolution = mapInstance?.getView().getResolutionForZoom(maxZoom) || 1; this.drawReaction(); } private drawReaction(): void { const reactionSquareFeature = this.getReactionSquare(); - this.features.push(reactionSquareFeature); - let lineFeature = this.getLineFeature(this.line); - this.features.push(lineFeature.lineFeature); - this.features.push(...lineFeature.arrowsFeatures); - this.products.forEach(product => { - lineFeature = this.getLineFeature(product.line); - this.features.push(lineFeature.lineFeature); - this.features.push(...lineFeature.arrowsFeatures); + this.reactionFeatures.push(reactionSquareFeature); + + const lineStringElements: Array<LineString> = []; + let lineStringWithArrows = this.getLineStringWithArrows(this.line); + lineStringElements.push(lineStringWithArrows.lineString); + this.reactionFeatures.push(...lineStringWithArrows.arrowsFeatures); + [...this.products, ...this.reactants, ...this.modifiers].forEach(element => { + lineStringWithArrows = this.getLineStringWithArrows(element.line); + lineStringElements.push(lineStringWithArrows.lineString); + this.reactionFeatures.push(...lineStringWithArrows.arrowsFeatures); }); - this.reactants.forEach(reactant => { - lineFeature = this.getLineFeature(reactant.line); - this.features.push(lineFeature.lineFeature); - this.features.push(...lineFeature.arrowsFeatures); + [...this.operators].forEach(operator => { + lineStringWithArrows = this.getLineStringWithArrows(operator.line); + lineStringElements.push(lineStringWithArrows.lineString); + this.reactionFeatures.push(...lineStringWithArrows.arrowsFeatures); + this.reactionFeatures.push(this.getOperator(operator)); }); - this.operators.forEach(operator => { - lineFeature = this.getLineFeature(operator.line); - this.features.push(lineFeature.lineFeature); - this.features.push(...lineFeature.arrowsFeatures); - this.features.push(this.getOperator(operator)); - }); - this.modifiers.forEach(modifier => { - lineFeature = this.getLineFeature(modifier.line); - this.features.push(lineFeature.lineFeature); - this.features.push(...lineFeature.arrowsFeatures); + + const multiLineString = new MultiLineString(lineStringElements); + + this.lineFeature = new Feature<MultiLineString>({ + geometry: multiLineString, + id: this.id, + type: FEATURE_TYPE.REACTION, + elementType: REACTION_ELEMENT_TYPES.LINE, + zIndex: this.zIndex, }); + this.lineFeature.setStyle(this.getLineStyle.bind(this)); } - private getLineFeature(line: Line): { - lineFeature: Feature<LineString>; + private getLineStringWithArrows(line: Line): { + lineString: LineString; arrowsFeatures: Array<Feature<MultiPolygon>>; } { const arrowsFeatures: Array<Feature<MultiPolygon>> = []; - const points = line.segments - .map((segment, index) => { - if (index === 0) { - return [ - this.pointToProjection({ x: segment.x1, y: segment.y1 }), - this.pointToProjection({ x: segment.x2, y: segment.y2 }), - ]; - } - return [this.pointToProjection({ x: segment.x2, y: segment.y2 })]; - }) - .flat(); + const points = getLineSegments(line, this.pointToProjection); if (line.startArrow.arrowType !== 'NONE') { const firstSegment = line.segments[0]; @@ -168,8 +170,7 @@ export default class Reaction { }); if (startArrowFeature) { startArrowFeature.set('elementType', REACTION_ELEMENT_TYPES.ARROW); - startArrowFeature.set('lineWidth', line.width); - startArrowFeature.setStyle(this.getStyle.bind(this)); + startArrowFeature.setStyle(this.getReactionObjectStyle.bind(this)); arrowsFeatures.push(startArrowFeature); } } @@ -196,8 +197,7 @@ export default class Reaction { }); if (endArrowFeature) { endArrowFeature.set('elementType', REACTION_ELEMENT_TYPES.ARROW); - endArrowFeature.set('lineWidth', line.width); - endArrowFeature.setStyle(this.getStyle.bind(this)); + endArrowFeature.setStyle(this.getReactionObjectStyle.bind(this)); arrowsFeatures.push(endArrowFeature); } } @@ -212,18 +212,17 @@ export default class Reaction { lineDash, zIndex: this.zIndex, }); - const lineFeature = new Feature<LineString>({ - geometry: lineString, - style: lineStyle, - lineWidth: line.width, - id: this.id, - type: FEATURE_TYPE.REACTION, - elementType: REACTION_ELEMENT_TYPES.LINE, - zIndex: this.zIndex, - }); - lineFeature.setStyle(this.getStyle.bind(this)); + lineString.set( + 'strokeStyle', + getStroke({ + color: rgbToHex(line.color), + width: line.width, + lineDash, + }), + ); + this.lineStyles.push(lineStyle); - return { lineFeature, arrowsFeatures }; + return { lineString, arrowsFeatures }; } private getReactionSquare(): Feature<MultiPolygon> { @@ -252,6 +251,13 @@ export default class Reaction { borderColor: this.line.color, zIndex: this.zIndex + 1, }); + squarePolygon.set( + 'strokeStyle', + getStroke({ + color: rgbToHex(this.line.color), + width: this.line.width, + }), + ); squarePolygon.rotate( squareRotation, this.pointToProjection({ @@ -271,7 +277,7 @@ export default class Reaction { elementType: REACTION_ELEMENT_TYPES.SQUARE, zIndex: this.zIndex, }); - squareFeature.setStyle(this.getStyle.bind(this)); + squareFeature.setStyle(this.getReactionObjectStyle.bind(this)); return squareFeature; } @@ -304,6 +310,12 @@ export default class Reaction { borderColor: operator.line.color, fillColor: operator.line.color, }); + circle.set( + 'strokeStyle', + getStroke({ + color: rgbToHex(operator.line.color), + }), + ); if (operator.operatorText) { circleStyle.getFill()?.setColor(rgbToHex(WHITE_COLOR)); @@ -328,7 +340,7 @@ export default class Reaction { fontSize: 10, zIndex: this.zIndex, }); - circleFeature.setStyle(this.getStyle.bind(this)); + circleFeature.setStyle(this.getReactionObjectStyle.bind(this)); return circleFeature; } @@ -339,7 +351,7 @@ export default class Reaction { }); } - protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void { + protected getLineStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void { if (!(feature instanceof Feature)) { return undefined; } @@ -350,34 +362,65 @@ export default class Reaction { feature.set('hidden', false); const styles: Array<Style> = []; - const maxZoom = this.mapInstance?.getView().get('originalMaxZoom'); - const minResolution = this.mapInstance?.getView().getResolutionForZoom(maxZoom); - const style = feature.get('style'); - if (!minResolution || !style) { - return []; - } - - const scale = minResolution / resolution; - const lineWidth = feature.get('lineWidth'); + const scale = this.minResolution / resolution; const type = feature.get('elementType'); const fontSize = feature.get('fontSize'); + let strokeStyle: Stroke | undefined; if (type === REACTION_ELEMENT_TYPES.OPERATOR && fontSize * scale <= 4) { return []; } - if (type === REACTION_ELEMENT_TYPES.ARROW && scale <= 0.08) { + if (type === REACTION_ELEMENT_TYPES.ARROW && scale <= 0.125) { return []; } + this.lineStyles.forEach(style => { + const styleGeometry = style.getGeometry(); + if (styleGeometry instanceof Polygon || styleGeometry instanceof LineString) { + strokeStyle = styleGeometry.get('strokeStyle'); + } + styles.push(getScaledElementStyle(style, strokeStyle, scale)); + }); + + return styles; + } + + protected getReactionObjectStyle( + feature: FeatureLike, + resolution: number, + ): Style | Array<Style> | void { + if (!(feature instanceof Feature)) { + return undefined; + } + if (this.isAnyOfElementsHidden()) { + feature.set('hidden', true); + return undefined; + } + feature.set('hidden', false); + + const styles: Array<Style> = []; + const style = feature.get('style'); + const scale = this.minResolution / resolution; + let strokeStyle: Stroke | undefined; + + if (scale <= 0.125) { + return []; + } + + const stylesToProcess: Array<Style> = []; if (style instanceof Style) { - styles.push(getScaledElementStyle(style, lineWidth, scale)); + stylesToProcess.push(style); } else if (Array.isArray(style)) { - style.forEach(singleStyle => { - if (singleStyle instanceof Style) { - styles.push(getScaledElementStyle(singleStyle, lineWidth, scale)); - } - }); + stylesToProcess.push(...style); } + stylesToProcess.forEach(singleStyle => { + const styleGeometry = singleStyle.getGeometry(); + if (styleGeometry instanceof Polygon || styleGeometry instanceof LineString) { + strokeStyle = styleGeometry.get('strokeStyle'); + } + styles.push(getScaledElementStyle(singleStyle, strokeStyle, scale)); + }); + return styles; } } diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts index f4a4f77a1cbda590ae52c23d94b63910157ae0bb..27c0c5157ed279f2649869eb27a016fd6628856a 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle.ts @@ -1,17 +1,26 @@ +/* eslint-disable no-magic-numbers */ import Style from 'ol/style/Style'; +import { Stroke } from 'ol/style'; export default function getScaledElementStyle( style: Style, - lineWidth: number, + strokeStyle: Stroke | undefined, scale: number, ): Style { - const clonedStyle = style.clone(); - const lineDash = clonedStyle.getStroke()?.getLineDash(); - if (lineDash) { - const newLineDash = lineDash.map(width => width * scale); - clonedStyle.getStroke()?.setLineDash(newLineDash); + if (strokeStyle) { + const lineDash = strokeStyle.getLineDash(); + let newLineDash: Array<number> = []; + if (lineDash) { + newLineDash = lineDash.map(width => width * scale); + } + const newStrokeStyle = new Stroke({ + color: strokeStyle.getColor(), + width: (strokeStyle.getWidth() || 1) * scale, + lineDash: newLineDash, + }); + + style.setStroke(newStrokeStyle); } - clonedStyle.getStroke()?.setWidth(lineWidth * scale); - clonedStyle.getText()?.setScale(scale); - return clonedStyle; + style.getText()?.setScale(scale); + return style; } diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.ts index a639ebd1dfe90648dfab7edc89855b406a6d565b..cdf5b8f5c668c8336b7e332d854d2c72c9aa874e 100644 --- a/src/components/Map/MapViewer/utils/config/useOlMapView.ts +++ b/src/components/Map/MapViewer/utils/config/useOlMapView.ts @@ -1,5 +1,5 @@ /* eslint-disable no-magic-numbers */ -import { EXTENT_PADDING_MULTIPLICATOR, OPTIONS, ZOOM_RESCALING_FACTOR } from '@/constants/map'; +import { OPTIONS, ZOOM_RESCALING_FACTOR } from '@/constants/map'; import { mapDataInitialPositionSelector, mapDataSizeSelector } from '@/redux/map/map.selectors'; import { MapInstance, Point } from '@/types/map'; import { usePointToProjection } from '@/utils/map/usePointToProjection'; @@ -19,23 +19,32 @@ export const useOlMapView = ({ mapInstance }: UseOlMapViewInput): MapConfig['vie const pointToProjection = usePointToProjection(); const extent = useMemo((): Extent => { - const extentPadding = { - horizontal: mapSize.width * EXTENT_PADDING_MULTIPLICATOR, - vertical: mapSize.height * EXTENT_PADDING_MULTIPLICATOR, - }; + let widthPadding = 0; + let heightPadding = 0; + let mapInstanceWidthToHeightRatio = 2.15; + const mapInstanceSize = mapInstance?.getSize(); + if (mapInstanceSize) { + mapInstanceWidthToHeightRatio = mapInstanceSize[0] / mapInstanceSize[1]; + } + const mapWidthToHeightRatio = mapSize.width / mapSize.height; + if (mapWidthToHeightRatio < mapInstanceWidthToHeightRatio) { + widthPadding = mapSize.height * mapInstanceWidthToHeightRatio - mapSize.width; + } else { + heightPadding = mapSize.width / mapInstanceWidthToHeightRatio - mapSize.height; + } const topLeftPoint: Point = { - x: mapSize.width + extentPadding.horizontal, - y: mapSize.height + extentPadding.vertical, + x: mapSize.width + widthPadding / 2, + y: mapSize.height + heightPadding / 2, }; const bottomRightPoint: Point = { - x: -extentPadding.horizontal, - y: -extentPadding.vertical, + x: -widthPadding / 2, + y: -heightPadding / 2, }; return boundingExtent([topLeftPoint, bottomRightPoint].map(pointToProjection)); - }, [pointToProjection, mapSize]); + }, [mapSize.width, mapSize.height, mapInstance, pointToProjection]); const center = useMemo((): Point => { const centerPoint: Point = { diff --git a/src/constants/map.ts b/src/constants/map.ts index 5856c87cf9eaa8aafee6d1aed74a4e57b9b70b16..d7efb594341aee93bf286ec885e543fb87036e8e 100644 --- a/src/constants/map.ts +++ b/src/constants/map.ts @@ -12,7 +12,7 @@ export const DEFAULT_CENTER_Y = 0; export const LATLNG_FALLBACK: LatLng = [0, 0]; export const EXTENT_PADDING_MULTIPLICATOR = 1; -export const ZOOM_RESCALING_FACTOR = 2; +export const ZOOM_RESCALING_FACTOR = 1; export const DEFAULT_CENTER_POINT: Point = { x: DEFAULT_CENTER_X, diff --git a/src/models/layerSchema.ts b/src/models/layerSchema.ts index be2ba10b1a0fe09971903c64ff8128e564244366..380146a7d8abc2d72931291915a4012d67746dbc 100644 --- a/src/models/layerSchema.ts +++ b/src/models/layerSchema.ts @@ -2,9 +2,7 @@ import { z } from 'zod'; export const layerSchema = z.object({ id: z.number(), - layerId: z.string(), name: z.string(), visible: z.boolean(), locked: z.boolean(), - empty: z.boolean(), }); diff --git a/src/redux/layers/layers.reducers.test.ts b/src/redux/layers/layers.reducers.test.ts index ffc0d92384518dde4ef3e126133e30568e74c220..158ed1b037f696342238bacd298fafe2483a045b 100644 --- a/src/redux/layers/layers.reducers.test.ts +++ b/src/redux/layers/layers.reducers.test.ts @@ -64,7 +64,7 @@ describe('layers reducer', () => { }, ], layersVisibility: { - [layersFixture.content[0].layerId]: layersFixture.content[0].visible, + [layersFixture.content[0].id]: layersFixture.content[0].visible, }, }); }); @@ -119,7 +119,7 @@ describe('layers reducer', () => { }, ], layersVisibility: { - [layersFixture.content[0].layerId]: layersFixture.content[0].visible, + [layersFixture.content[0].id]: layersFixture.content[0].visible, }, }); expect(promiseFulfilled).toEqual('succeeded'); diff --git a/src/redux/layers/layers.reducers.ts b/src/redux/layers/layers.reducers.ts index 0cc1ad73bf6c4e7625d657791389ef5befe48fc5..b0e601d57acd4fbe5bdf5b89d80ddb8d4a3a6e7e 100644 --- a/src/redux/layers/layers.reducers.ts +++ b/src/redux/layers/layers.reducers.ts @@ -39,7 +39,7 @@ export const getLayersForModelReducer = (builder: ActionReducerMapBuilder<Layers export const setLayerVisibilityReducer = ( state: LayersState, - action: PayloadAction<{ modelId: number; visible: boolean; layerId: string }>, + action: PayloadAction<{ modelId: number; visible: boolean; layerId: number }>, ): void => { const { modelId, visible, layerId } = action.payload; const { data } = state[modelId]; diff --git a/src/redux/layers/layers.thunks.test.ts b/src/redux/layers/layers.thunks.test.ts index bdc626d56da950187f9c9d5f57970e12e8a9597b..234d9950d59c21be0a60a830d4ce7e8cfc755db8 100644 --- a/src/redux/layers/layers.thunks.test.ts +++ b/src/redux/layers/layers.thunks.test.ts @@ -51,7 +51,7 @@ describe('layers thunks', () => { }, ], layersVisibility: { - [layersFixture.content[0].layerId]: layersFixture.content[0].visible, + [layersFixture.content[0].id]: layersFixture.content[0].visible, }, }); }); diff --git a/src/redux/layers/layers.thunks.ts b/src/redux/layers/layers.thunks.ts index 0f38e3d1256a8c2d0ec34581cee2fb129cddfc74..f1594875f0597b512a6e8d6c4b4becb063ac8425 100644 --- a/src/redux/layers/layers.thunks.ts +++ b/src/redux/layers/layers.thunks.ts @@ -53,7 +53,7 @@ export const getLayersForModel = createAsyncThunk< ); }); const layersVisibility = layers.reduce((acc: { [key: string]: boolean }, layer) => { - acc[layer.details.layerId] = layer.details.visible; + acc[layer.details.id] = layer.details.visible; return acc; }, {}); return { diff --git a/src/redux/layers/layers.types.ts b/src/redux/layers/layers.types.ts index c00d6a7e63889b392919154ee1be634e5ed6f90e..701a499abc9979cc38b900ce64d25943a58935ef 100644 --- a/src/redux/layers/layers.types.ts +++ b/src/redux/layers/layers.types.ts @@ -10,7 +10,7 @@ export type LayerState = { }; export type LayerVisibilityState = { - [key: string]: boolean; + [key: number]: boolean; }; export type LayersVisibilitiesState = {