Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • minerva/frontend
1 result
Show changes
Commits on Source (6)
Showing
with 153 additions and 58 deletions
......@@ -48,7 +48,7 @@ export const BioEntityDrawer = (): React.ReactNode => {
).filter(modificationResidue => modificationResidue.state && modificationResidue.state !== '');
const isModificationAvailable = modificationResidues.length > ZERO;
const type = getTypeBySBOTerm(bioEntityData.sboTerm);
const type = getTypeBySBOTerm(bioEntityData.sboTerm, bioEntityData.shape);
return (
<div className="h-calc-drawer" data-testid="bioentity-drawer">
......
......@@ -14,6 +14,11 @@ export const BLACK_COLOR: Color = {
rgb: -16777216,
};
export const TRANSPARENT_COLOR: Color = {
alpha: 0,
rgb: 0,
};
export const REACTION_ELEMENT_TYPES = {
OPERATOR: 'operator',
SQUARE: 'square',
......
......@@ -16,24 +16,65 @@ import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mapS
import { FEATURE_TYPE } from '@/constants/features';
import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction';
function isFeatureFilledCompartment(feature: FeatureLike): boolean {
return feature.get('type') === FEATURE_TYPE.COMPARTMENT && feature.get('filled');
}
function isFeatureNotCompartment(feature: FeatureLike): boolean {
return (
[...Object.values(FEATURE_TYPE)].includes(feature.get('type')) &&
feature.get('type') !== FEATURE_TYPE.COMPARTMENT
);
}
/* prettier-ignore */
export const onMapLeftClick =
(mapSize: MapSize, modelId: number, dispatch: AppDispatch, isResultDrawerOpen: boolean, comments: Comment[], modelElements: Array<ModelElement>, reactions: Array<NewReaction>) =>
async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => {
(
mapSize: MapSize,
modelId: number,
dispatch: AppDispatch,
isResultDrawerOpen: boolean,
comments: Comment[],
modelElements: Array<ModelElement>,
reactions: Array<NewReaction>,
) =>
async (
{ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>,
mapInstance: Map,
): Promise<void> => {
const [lng, lat] = toLonLat(coordinate);
const point = latLngToPoint([lat, lng], mapSize);
dispatch(updateLastClick({ coordinates: point, modelId }));
let featureAtPixel: FeatureLike | undefined;
mapInstance.forEachFeatureAtPixel(pixel, (feature, ) => {
if(feature.get('id') && [...Object.values(FEATURE_TYPE)].includes(feature.get('type')) && feature.get('zIndex') >= 0) {
featureAtPixel = feature;
return true;
mapInstance.forEachFeatureAtPixel(
pixel,
feature => {
const featureZIndex = feature.get('zIndex');
if (
(isFeatureFilledCompartment(feature) || isFeatureNotCompartment(feature)) &&
(featureZIndex === undefined || featureZIndex >= 0) &&
!feature.get('hidden')
) {
featureAtPixel = feature;
return true;
}
return false;
},
{ hitTolerance: 10 },
);
if (featureAtPixel) {
const { shouldBlockCoordSearch } = handleFeaturesClick([featureAtPixel], dispatch, comments);
if (shouldBlockCoordSearch) {
return;
}
return false;
}, {hitTolerance: 10});
if(!featureAtPixel) {
}
dispatch(handleDataReset);
if (!featureAtPixel) {
if (isResultDrawerOpen) {
dispatch(closeDrawer());
}
......@@ -43,19 +84,11 @@ export const onMapLeftClick =
return;
}
const { shouldBlockCoordSearch } = handleFeaturesClick([featureAtPixel], dispatch, comments);
if (shouldBlockCoordSearch) {
return;
}
dispatch(handleDataReset);
const type = featureAtPixel.get('type');
const id = featureAtPixel.get('id');
if([FEATURE_TYPE.ALIAS, FEATURE_TYPE.GLYPH].includes(type)) {
if ([FEATURE_TYPE.ALIAS, FEATURE_TYPE.GLYPH, FEATURE_TYPE.COMPARTMENT].includes(type)) {
await leftClickHandleAlias(dispatch)(featureAtPixel, modelId);
} else if (type === FEATURE_TYPE.REACTION) {
clickHandleReaction(dispatch)(modelElements, reactions, id, modelId);
clickHandleReaction(dispatch)(modelElements, reactions, id, modelId);
}
};
......@@ -23,6 +23,8 @@ export const onMapRightClick =
const [lng, lat] = toLonLat(coordinate);
const point = latLngToPoint([lat, lng], mapSize);
dispatch(updateLastRightClick({ coordinates: point, modelId }));
dispatch(handleDataReset);
dispatch(openContextMenu(pixel));
let foundFeature: Feature | undefined;
mapInstance.getAllLayers().forEach(layer => {
......@@ -31,11 +33,14 @@ export const onMapRightClick =
const source = layer.getSource();
if (source instanceof VectorSource) {
foundFeature = source.getClosestFeatureToCoordinate(coordinate, (feature) => {
return [
FEATURE_TYPE.ALIAS,
FEATURE_TYPE.REACTION,
FEATURE_TYPE.GLYPH
].includes(feature.get('type')) && feature.get('zIndex') >= 0;
return (
feature.get('type') === FEATURE_TYPE.COMPARTMENT && feature.get('filled') ||
[
FEATURE_TYPE.ALIAS,
FEATURE_TYPE.REACTION,
FEATURE_TYPE.GLYPH
].includes(feature.get('type'))
) && feature.get('zIndex') >= 0 && !feature.get('hidden');
});
}
}
......@@ -44,12 +49,10 @@ export const onMapRightClick =
if(!foundFeature) {
return;
}
dispatch(handleDataReset);
dispatch(openContextMenu(pixel));
const type = foundFeature.get('type');
const id = foundFeature.get('id');
if([FEATURE_TYPE.ALIAS, FEATURE_TYPE.GLYPH].includes(type)) {
if([FEATURE_TYPE.ALIAS, FEATURE_TYPE.GLYPH, FEATURE_TYPE.COMPARTMENT].includes(type)) {
const modelElement = modelElements.find(element => element.id === id);
if(!modelElement) {
return;
......
......@@ -30,6 +30,7 @@ export default function processModelElements(
mapBackgroundType: number,
mapSize: MapSize,
): Array<MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph> {
const overlaysVisible = Boolean(overlaysOrder.length);
const validElements: Array<
MapElement | CompartmentCircle | CompartmentSquare | CompartmentPathway | Glyph
> = [];
......@@ -74,7 +75,7 @@ export default function processModelElements(
nameHorizontalAlign: element.nameHorizontalAlign as HorizontalAlign,
text: element.name,
fontSize: element.fontSize,
overlaysVisible: Boolean(overlaysOrder.length),
overlaysVisible,
pointToProjection,
mapInstance,
vectorSource,
......@@ -127,6 +128,7 @@ export default function processModelElements(
bioShapes: shapes,
overlays: groupedElementsOverlays[element.id],
overlaysOrder,
overlaysVisible,
getOverlayColor,
mapBackgroundType,
mapSize,
......
......@@ -39,6 +39,8 @@ import MarkerOverlay from '@/components/Map/MapViewer/MapViewerVector/utils/shap
import processModelElements from '@/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/processModelElements';
import useDebouncedValue from '@/utils/useDebouncedValue';
import { mapBackgroundTypeSelector, mapDataSizeSelector } from '@/redux/map/map.selectors';
import MapBackgroundsEnum from '@/redux/map/map.enums';
import { setMapBackgroundType } from '@/redux/map/map.slice';
export const useOlMapReactionsLayer = ({
mapInstance,
......@@ -73,6 +75,12 @@ export const useOlMapReactionsLayer = ({
}
}, [currentModelId, dispatch]);
useEffect(() => {
if (overlaysOrder.length) {
dispatch(setMapBackgroundType(MapBackgroundsEnum.NETWORK));
}
}, [dispatch, overlaysOrder]);
const groupedElementsOverlays = useMemo(() => {
const elementsBioEntitesOverlay = debouncedBioEntities.filter(
bioEntity => bioEntity.type !== 'line',
......
......@@ -48,6 +48,7 @@ export interface BaseMapElementProps {
fillColor: Color;
borderColor: Color;
pointToProjection: UsePointToProjectionResult;
overlaysVisible: boolean;
vectorSource: VectorSource;
mapBackgroundType: number;
mapSize: MapSize;
......@@ -104,6 +105,8 @@ export default abstract class BaseMultiPolygon {
pointToProjection: UsePointToProjectionResult;
overlaysVisible: boolean;
vectorSource: VectorSource;
mapBackgroundType: number;
......@@ -138,6 +141,7 @@ export default abstract class BaseMultiPolygon {
fillColor,
borderColor,
pointToProjection,
overlaysVisible,
vectorSource,
mapBackgroundType,
mapSize,
......@@ -163,6 +167,7 @@ export default abstract class BaseMultiPolygon {
this.nameHorizontalAlign = nameHorizontalAlign;
this.fillColor = fillColor;
this.borderColor = borderColor;
this.overlaysVisible = overlaysVisible;
this.pointToProjection = pointToProjection;
this.vectorSource = vectorSource;
this.mapBackgroundType = mapBackgroundType;
......@@ -332,7 +337,11 @@ export default abstract class BaseMultiPolygon {
textStyle.setText(text);
}
if (strokeStyle && lineWidth) {
if (lineWidth * scale < 0.08 && this.sboTerm !== COMPLEX_SBO_TERM) {
if (
!this.overlaysVisible &&
lineWidth * scale < 0.08 &&
this.sboTerm !== COMPLEX_SBO_TERM
) {
clonedStyle.setStroke(null);
} else {
strokeStyle.setWidth(lineWidth * scale);
......
......@@ -13,7 +13,10 @@ import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shape
import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
import { MapInstance } from '@/types/map';
import { Color } from '@/types/models';
import { MAP_ELEMENT_TYPES } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
import {
MAP_ELEMENT_TYPES,
TRANSPARENT_COLOR,
} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
import VectorSource from 'ol/source/Vector';
import { MapSize } from '@/redux/map/map.types';
......@@ -113,6 +116,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
fillColor,
borderColor,
pointToProjection,
overlaysVisible,
vectorSource,
mapBackgroundType,
mapSize,
......@@ -148,7 +152,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
new Style({
geometry: framePolygon,
fill: this.overlaysVisible
? undefined
? getFill({ color: rgbToHex(TRANSPARENT_COLOR) })
: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 128 }) }),
zIndex: this.zIndex,
}),
......@@ -179,7 +183,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
? getStroke({ width: this.innerWidth })
: getStroke({ color: rgbToHex(this.borderColor), width: this.innerWidth }),
fill: this.overlaysVisible
? undefined
? getFill({ color: rgbToHex(TRANSPARENT_COLOR) })
: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 9 }) }),
zIndex: this.zIndex,
}),
......
......@@ -8,6 +8,7 @@ import {
import {
BLACK_COLOR,
MAP_ELEMENT_TYPES,
TRANSPARENT_COLOR,
WHITE_COLOR,
} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
import Polygon from 'ol/geom/Polygon';
......@@ -104,6 +105,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
fillColor,
borderColor,
pointToProjection,
overlaysVisible,
vectorSource,
mapBackgroundType,
mapSize,
......@@ -136,7 +138,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
getStyle({
geometry: compartmentPolygon,
borderColor: this.borderColor,
fillColor: this.overlaysVisible ? undefined : { ...this.fillColor, alpha: 9 },
fillColor: this.overlaysVisible ? TRANSPARENT_COLOR : { ...this.fillColor, alpha: 9 },
lineWidth: this.outerWidth,
zIndex: this.zIndex,
}),
......
/* eslint-disable no-magic-numbers */
import { Feature, Map, View } from 'ol';
import { Style, Icon } from 'ol/style';
import { Style } from 'ol/style';
import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
import Glyph, {
GlyphProps,
} from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Glyph';
import { MapInstance } from '@/types/map';
import Polygon from 'ol/geom/Polygon';
import { BASE_NEW_API_URL } from '@/constants';
describe('Glyph', () => {
let props: GlyphProps;
......@@ -57,9 +56,6 @@ describe('Glyph', () => {
]);
expect(glyph.style).toBeInstanceOf(Style);
const image = glyph.style.getImage() as Icon;
expect(image).toBeInstanceOf(Icon);
expect(image.getSrc()).toBe(`${BASE_NEW_API_URL}projects/pdmap_appu_test/glyphs/1/fileContent`);
});
it('should scale image based on map resolution', () => {
......
......@@ -29,7 +29,9 @@ export type GlyphProps = {
export default class Glyph {
feature: Feature<Polygon>;
style: Style;
style: Style = new Style({});
imageScale: number = 1;
polygonStyle: Style;
......@@ -88,7 +90,7 @@ export default class Glyph {
borderColor: { ...WHITE_COLOR, alpha: 0 },
fillColor: { ...WHITE_COLOR, alpha: 0 },
});
const iconFeature = new Feature({
this.feature = new Feature({
geometry: polygon,
id: elementId,
type: FEATURE_TYPE.GLYPH,
......@@ -112,16 +114,23 @@ export default class Glyph {
return { anchor: [anchorX, anchorY], coords: center || [0, 0] };
},
});
this.style = new Style({
image: new Icon({
anchor: [0, 0],
src: `${BASE_NEW_API_URL}${apiPath.getGlyphImage(glyphId)}`,
size: [width, height],
}),
zIndex,
});
iconFeature.setStyle(this.getStyle.bind(this));
this.feature = iconFeature;
const img = new Image();
img.onload = (): void => {
const imageWidth = img.naturalWidth;
const imageHeight = img.naturalHeight;
this.imageScale = width / imageWidth;
this.style = new Style({
image: new Icon({
anchor: [0, 0],
img,
size: [imageWidth, imageHeight],
}),
zIndex,
});
this.feature.setStyle(this.getStyle.bind(this));
};
img.src = `${BASE_NEW_API_URL}${apiPath.getGlyphImage(glyphId)}`;
}
protected getStyle(feature: FeatureLike, resolution: number): Style | Array<Style> | void {
......@@ -139,7 +148,7 @@ export default class Glyph {
coords = anchorAndCoords.coords;
}
if (this.style.getImage()) {
this.style.getImage()?.setScale(imageScale * this.pixelRatio);
this.style.getImage()?.setScale(imageScale * this.pixelRatio * this.imageScale);
(this.style.getImage() as Icon).setAnchor(anchor);
this.style.setGeometry(new Point(coords));
}
......
......@@ -66,6 +66,7 @@ describe('MapElement', () => {
nameHorizontalAlign: 'CENTER',
pointToProjection: jest.fn(),
mapInstance,
overlaysVisible: false,
vectorSource: new VectorSource(),
getOverlayColor: (): string => '#ffffff',
mapBackgroundType: MapBackgroundsEnum.SEMANTIC,
......
......@@ -14,6 +14,7 @@ import {
import {
BLACK_COLOR,
MAP_ELEMENT_TYPES,
TRANSPARENT_COLOR,
WHITE_COLOR,
} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
import BaseMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon';
......@@ -65,6 +66,7 @@ export type MapElementProps = {
modifications?: Array<Modification>;
overlays?: Array<OverlayBioEntityRender>;
overlaysOrder?: Array<OverlayOrder>;
overlaysVisible: boolean;
getOverlayColor: GetOverlayBioEntityColorByAvailableProperties;
mapBackgroundType: number;
mapSize: MapSize;
......@@ -129,6 +131,7 @@ export default class MapElement extends BaseMultiPolygon {
modifications = [],
overlays = [],
overlaysOrder = [],
overlaysVisible,
getOverlayColor,
mapBackgroundType,
mapSize,
......@@ -157,6 +160,7 @@ export default class MapElement extends BaseMultiPolygon {
borderColor,
pointToProjection,
vectorSource,
overlaysVisible,
mapBackgroundType,
mapSize,
});
......@@ -275,7 +279,7 @@ export default class MapElement extends BaseMultiPolygon {
activityBorderPolygon.set('lineWidth', 1);
const activityBorderStyle = getStyle({
geometry: activityBorderPolygon,
fillColor: { rgb: 0, alpha: 0 },
fillColor: TRANSPARENT_COLOR,
lineDash: [3, 5],
zIndex: this.zIndex,
});
......@@ -299,7 +303,7 @@ export default class MapElement extends BaseMultiPolygon {
const elementStyle = getStyle({
geometry: elementPolygon,
borderColor: this.borderColor,
fillColor: this.overlaysOrder.length ? undefined : this.fillColor,
fillColor: this.overlaysVisible ? TRANSPARENT_COLOR : this.fillColor,
lineWidth: this.lineWidth,
lineDash: this.lineDash,
zIndex: this.zIndex,
......
......@@ -19,7 +19,10 @@ import getArrowFeature from '@/components/Map/MapViewer/MapViewerVector/utils/sh
import { FeatureLike } from 'ol/Feature';
import Style from 'ol/style/Style';
import { ArrowTypeDict, LineTypeDict } from '@/redux/shapes/shapes.types';
import { LAYER_ELEMENT_TYPES } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
import {
LAYER_ELEMENT_TYPES,
TRANSPARENT_COLOR,
} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants';
import getScaledElementStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getScaledElementStyle';
export interface LayerProps {
......@@ -174,7 +177,7 @@ export default class Layer {
const polygonStyle = getStyle({
geometry: polygon,
borderColor: oval.borderColor,
fillColor: { rgb: 0, alpha: 0 },
fillColor: TRANSPARENT_COLOR,
lineWidth: oval.lineWidth,
zIndex: oval.z,
});
......
......@@ -340,9 +340,14 @@ export default class Reaction {
}
protected getStyle(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 maxZoom = this.mapInstance?.getView().get('originalMaxZoom');
......
......@@ -190,7 +190,6 @@ describe('handleAliasResults - util', () => {
.reply(HttpStatusCode.Ok, bioEntityFixture);
const { store } = getReduxStoreWithActionsListener();
const { dispatch } = store;
await handleAliasResults(dispatch, ELEMENT_SEARCH_RESULT_MOCK_ALIAS, {
...SEARCH_CONFIG_MOCK,
isResultDrawerOpen: true,
......
......@@ -7,6 +7,7 @@ export const FEATURE_TYPE = {
ALIAS: 'ALIAS',
REACTION: 'REACTION',
GLYPH: 'GLYPH',
COMPARTMENT: 'COMPARTMENT',
} as const;
export const PIN_ICON_ANY = [
......
......@@ -53,6 +53,7 @@ export const bioEntitySchema = z.object({
charge: z.number().nullable().optional(),
substanceUnits: z.string().nullable().optional(),
onlySubstanceUnits: z.boolean().optional().nullable(),
shape: z.string().nullable().optional(),
modificationResidues: z.optional(z.array(modificationResiduesSchema)),
complex: z.number().nullable().optional(),
compartment: z.number().nullable().optional(),
......
export const getTypeBySBOTerm = (sbo: string | undefined): string => {
export const getTypeBySBOTerm = (sbo: string | undefined, shape?: string | null): string => {
switch (sbo) {
case 'SBO:0000334':
return 'Antisense RNA';
......@@ -29,6 +29,16 @@ export const getTypeBySBOTerm = (sbo: string | undefined): string => {
return 'Simple molecule';
case 'SBO:0000285':
return 'Unknown';
case 'SBO:0000290':
switch (shape) {
case 'PATHWAY':
return 'Pathway';
case 'SQUARE_COMPARTMENT':
case 'CIRCLE_COMPARTMENT':
return 'Compartment';
default:
return '---';
}
default:
return '---';
}
......