Skip to content
Snippets Groups Projects
Commit 4c1a0299 authored by Tadeusz Miesiąc's avatar Tadeusz Miesiąc
Browse files

Merge branch 'overlays-additional-rendering-conditions' into 'development'

Overlays additional rendering conditions

See merge request !81
parents ed9523e9 fe71c469
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!81Overlays additional rendering conditions
Pipeline #83445 failed
Showing
with 359 additions and 32 deletions
import { StoreType } from '@/redux/store';
import { render, screen } from '@testing-library/react';
import { act, render, screen } from '@testing-library/react';
import {
InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import {
BACKGROUNDS_MOCK,
BACKGROUND_INITIAL_STATE_MOCK,
} from '@/redux/backgrounds/background.mock';
import { initialMapStateFixture } from '@/redux/map/map.fixtures';
import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse';
import { OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK } from '@/redux/overlayBioEntity/overlayBioEntity.mock';
import { HttpStatusCode } from 'axios';
import { overlayBioEntityFixture } from '@/models/fixtures/overlayBioEntityFixture';
import { apiPath } from '@/redux/apiPath';
import { CORE_PD_MODEL_MOCK } from '@/models/mocks/modelsMock';
import { MODELS_INITIAL_STATE_MOCK } from '@/redux/models/models.mock';
import { parseOverlayBioEntityToOlRenderingFormat } from '@/redux/overlayBioEntity/overlayBioEntity.utils';
import { OverlayListItem } from './OverlayListItem.component';
const mockedAxiosNewClient = mockNetworkNewAPIResponse();
const DEFAULT_BACKGROUND_ID = 0;
const EMPTY_BACKGROUND_ID = 15;
const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
......@@ -29,8 +46,31 @@ describe('OverlayListItem - component', () => {
expect(screen.getByRole('button', { name: 'View' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument();
});
// TODO implement when connecting logic to component
it.skip('should trigger view overlays on view button click', () => {});
it('should trigger view overlays on view button click and switch background to Empty if available', async () => {
const OVERLAY_ID = 21;
const { store } = renderComponent({
map: initialMapStateFixture,
backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK },
overlayBioEntity: OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK,
models: { ...MODELS_INITIAL_STATE_MOCK, data: [CORE_PD_MODEL_MOCK] },
});
mockedAxiosNewClient
.onGet(apiPath.getOverlayBioEntity({ overlayId: OVERLAY_ID, modelId: 5053 }))
.reply(HttpStatusCode.Ok, overlayBioEntityFixture);
expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID);
const ViewButton = screen.getByRole('button', { name: 'View' });
await act(() => {
ViewButton.click();
});
expect(store.getState().map.data.backgroundId).toBe(EMPTY_BACKGROUND_ID);
expect(store.getState().overlayBioEntity.data).toEqual(
parseOverlayBioEntityToOlRenderingFormat(overlayBioEntityFixture, OVERLAY_ID),
);
});
// TODO implement when connecting logic to component
it.skip('should trigger download overlay to PC on download button click', () => {});
});
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { getOverlayBioEntityForAllModels } from '@/redux/overlayBioEntity/overlayBioEntity.thunk';
import { Button } from '@/shared/Button';
import { useEmptyBackground } from './hooks/useEmptyBackground';
interface OverlayListItemProps {
name: string;
......@@ -10,8 +11,10 @@ interface OverlayListItemProps {
export const OverlayListItem = ({ name, overlayId }: OverlayListItemProps): JSX.Element => {
const onDownloadOverlay = (): void => {};
const dispatch = useAppDispatch();
const { setBackgroundtoEmptyIfAvailable } = useEmptyBackground();
const onViewOverlay = (): void => {
setBackgroundtoEmptyIfAvailable();
dispatch(getOverlayBioEntityForAllModels({ overlayId }));
};
......
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { initialMapStateFixture } from '@/redux/map/map.fixtures';
import {
BACKGROUNDS_MOCK,
BACKGROUND_INITIAL_STATE_MOCK,
} from '@/redux/backgrounds/background.mock';
import { renderHook } from '@testing-library/react';
import { useEmptyBackground } from './useEmptyBackground';
const DEFAULT_BACKGROUND_ID = 0;
const EMPTY_BACKGROUND_ID = 15;
describe('useEmptyBackground - hook', () => {
describe('returns setEmptyBackground function', () => {
it('should not set background to "Empty" if its not available', () => {
const { Wrapper, store } = getReduxWrapperWithStore({
map: initialMapStateFixture,
backgrounds: BACKGROUND_INITIAL_STATE_MOCK,
});
const { result } = renderHook(() => useEmptyBackground(), { wrapper: Wrapper });
expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID);
result.current.setBackgroundtoEmptyIfAvailable();
expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID);
});
it('should set background to "Empty" if its available', () => {
const { Wrapper, store } = getReduxWrapperWithStore({
map: initialMapStateFixture,
backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK },
});
const { result } = renderHook(() => useEmptyBackground(), { wrapper: Wrapper });
expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID);
result.current.setBackgroundtoEmptyIfAvailable();
expect(store.getState().map.data.backgroundId).toBe(EMPTY_BACKGROUND_ID);
});
});
});
import { useCallback } from 'react';
import { emptyBackgroundIdSelector } from '@/redux/backgrounds/background.selectors';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { setMapBackground } from '@/redux/map/map.slice';
type UseEmptyBackgroundReturn = {
setBackgroundtoEmptyIfAvailable: () => void;
};
export const useEmptyBackground = (): UseEmptyBackgroundReturn => {
const dispatch = useAppDispatch();
const emptyBackgroundId = useAppSelector(emptyBackgroundIdSelector);
const setBackgroundtoEmptyIfAvailable = useCallback(() => {
if (emptyBackgroundId) {
dispatch(setMapBackground(emptyBackgroundId));
}
}, [dispatch, emptyBackgroundId]);
return { setBackgroundtoEmptyIfAvailable };
};
import { createOverlayGeometryFeature } from './createOverlayGeometryFeature';
describe('createOverlayGeometryFeature', () => {
it('should create a feature with the correct geometry and style', () => {
const xMin = 0;
const yMin = 0;
const xMax = 10;
const yMax = 10;
const colorHexString = '#FF0000';
const feature = createOverlayGeometryFeature([xMin, yMin, xMax, yMax], colorHexString);
expect(feature.getGeometry()!.getCoordinates()).toEqual([
[
[xMin, yMin],
[xMin, yMax],
[xMax, yMax],
[xMax, yMin],
[xMin, yMin],
],
]);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - getStyle() is not typed
expect(feature.getStyle().getFill().getColor()).toEqual(colorHexString);
});
it('should create a feature with the correct geometry and style when using a different color', () => {
const xMin = -5;
const yMin = -5;
const xMax = 5;
const yMax = 5;
const colorHexString = '#00FF00';
const feature = createOverlayGeometryFeature([xMin, yMin, xMax, yMax], colorHexString);
expect(feature.getGeometry()!.getCoordinates()).toEqual([
[
[xMin, yMin],
[xMin, yMax],
[xMax, yMax],
[xMax, yMin],
[xMin, yMin],
],
]);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - getStyle() is not typed
expect(feature.getStyle().getFill().getColor()).toEqual(colorHexString);
});
});
import { Fill, Style } from 'ol/style';
import { fromExtent } from 'ol/geom/Polygon';
import Feature from 'ol/Feature';
import type Polygon from 'ol/geom/Polygon';
export const createOverlayGeometryFeature = (
[xMin, yMin, xMax, yMax]: number[],
color: string,
): Feature<Polygon> => {
const feature = new Feature({ geometry: fromExtent([xMin, yMin, xMax, yMax]) });
feature.setStyle(new Style({ fill: new Fill({ color }) }));
return feature;
};
import { OverlayBioEntityRender } from '@/types/OLrendering';
import { getColorByAvailableProperties } from './getColorByAvailableProperties';
describe('getColorByAvailableProperties', () => {
const ENTITY: OverlayBioEntityRender = {
id: 0,
modelId: 0,
x1: 0,
y1: 0,
x2: 0,
y2: 0,
width: 0,
height: 0,
value: null,
overlayId: 0,
color: null,
};
const getHexTricolorGradientColorWithAlpha = jest.fn().mockReturnValue('#FFFFFF');
const defaultColor = '#000000';
beforeEach(() => {
jest.clearAllMocks();
});
it('should return the result of getHexTricolorGradientColorWithAlpha if entity has a value equal to 0', () => {
const entity = { ...ENTITY, value: 0 };
const result = getColorByAvailableProperties(
entity,
getHexTricolorGradientColorWithAlpha,
defaultColor,
);
expect(result).toEqual('#FFFFFF');
expect(getHexTricolorGradientColorWithAlpha).toHaveBeenCalledWith(entity.value);
});
it('should return the result of getHexTricolorGradientColorWithAlpha if entity has a value', () => {
const entity = { ...ENTITY, value: -0.2137 };
const result = getColorByAvailableProperties(
entity,
getHexTricolorGradientColorWithAlpha,
defaultColor,
);
expect(result).toEqual('#FFFFFF');
expect(getHexTricolorGradientColorWithAlpha).toHaveBeenCalledWith(entity.value);
});
it('should return the result of convertDecimalToHex if entity has a color', () => {
const entity = { ...ENTITY, color: { rgb: -65536, alpha: 0 } }; // red color
const result = getColorByAvailableProperties(
entity,
getHexTricolorGradientColorWithAlpha,
defaultColor,
);
expect(result).toEqual('#ff0000');
expect(getHexTricolorGradientColorWithAlpha).not.toHaveBeenCalled();
});
it('should return the default color if entity has neither a value nor a color', () => {
const result = getColorByAvailableProperties(
ENTITY,
getHexTricolorGradientColorWithAlpha,
defaultColor,
);
expect(result).toEqual('#000000');
expect(getHexTricolorGradientColorWithAlpha).not.toHaveBeenCalled();
});
});
import { ZERO } from '@/constants/common';
import type { GetHex3ColorGradientColorWithAlpha } from '@/hooks/useTriColorLerp';
import { OverlayBioEntityRender } from '@/types/OLrendering';
import { convertDecimalToHex } from '@/utils/convert/convertDecimalToHex';
export const getColorByAvailableProperties = (
entity: OverlayBioEntityRender,
getHexTricolorGradientColorWithAlpha: GetHex3ColorGradientColorWithAlpha,
defaultColor: string,
): string => {
if (typeof entity.value === 'number') {
return getHexTricolorGradientColorWithAlpha(entity.value || ZERO);
}
if (entity.color) {
return convertDecimalToHex(entity.color.rgb);
}
return defaultColor;
};
import { ZERO } from '@/constants/common';
import { GetHex3ColorGradientColorWithAlpha } from '@/hooks/useTriColorLerp';
import type { GetHex3ColorGradientColorWithAlpha } from '@/hooks/useTriColorLerp';
import { OverlayBioEntityRender } from '@/types/OLrendering';
import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
import Feature from 'ol/Feature';
import Polygon, { fromExtent } from 'ol/geom/Polygon';
import { Fill, Style } from 'ol/style';
import type Feature from 'ol/Feature';
import type Polygon from 'ol/geom/Polygon';
import { createOverlayGeometryFeature } from './createOverlayGeometryFeature';
import { getColorByAvailableProperties } from './getColorByAvailableProperties';
export const getOverlayFeatures = (
bioEntities: OverlayBioEntityRender[],
pointToProjection: UsePointToProjectionResult,
getHex3ColorGradientColorWithAlpha: GetHex3ColorGradientColorWithAlpha,
): Feature<Polygon>[] =>
bioEntities.map(entity => {
const feature = new Feature({
geometry: fromExtent([
type GetOverlayFeaturesProps = {
bioEntities: OverlayBioEntityRender[];
pointToProjection: UsePointToProjectionResult;
getHex3ColorGradientColorWithAlpha: GetHex3ColorGradientColorWithAlpha;
defaultColor: string;
};
export const getOverlayFeatures = ({
bioEntities,
pointToProjection,
getHex3ColorGradientColorWithAlpha,
defaultColor,
}: GetOverlayFeaturesProps): Feature<Polygon>[] =>
bioEntities.map(entity =>
createOverlayGeometryFeature(
[
...pointToProjection({ x: entity.x1, y: entity.y1 }),
...pointToProjection({ x: entity.x2, y: entity.y2 }),
]),
});
feature.setStyle(
new Style({
fill: new Fill({ color: getHex3ColorGradientColorWithAlpha(entity.value || ZERO) }),
}),
);
return feature;
});
],
getColorByAvailableProperties(entity, getHex3ColorGradientColorWithAlpha, defaultColor),
),
);
......@@ -10,12 +10,18 @@ import { getOverlayFeatures } from './getOverlayFeatures';
export const useOlMapOverlaysLayer = (): VectorLayer<VectorSource<Geometry>> => {
const pointToProjection = usePointToProjection();
const { getHex3ColorGradientColorWithAlpha } = useTriColorLerp();
const { getHex3ColorGradientColorWithAlpha, defaultColorHex } = useTriColorLerp();
const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector);
const features = useMemo(
() => getOverlayFeatures(bioEntities, pointToProjection, getHex3ColorGradientColorWithAlpha),
[bioEntities, getHex3ColorGradientColorWithAlpha, pointToProjection],
() =>
getOverlayFeatures({
bioEntities,
pointToProjection,
getHex3ColorGradientColorWithAlpha,
defaultColor: defaultColorHex,
}),
[bioEntities, getHex3ColorGradientColorWithAlpha, pointToProjection, defaultColorHex],
);
const vectorSource = useMemo(() => {
......
export const EMPTY_BACKGROUND_NAME = 'Empty';
export const WHITE_HEX_OPACITY_0 = '#00000000';
import { useCallback } from 'react';
import { WHITE_HEX_OPACITY_0 } from '@/constants/hexColors';
import {
maxColorValSelector,
minColorValSelector,
neutralColorValSelector,
overlayOpacitySelector,
simpleColorValSelector,
} from '@/redux/configuration/configuration.selectors';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { getHexTricolorGradientColorWithAlpha } from '@/utils/convert/getHexTricolorGradientColorWithAlpha';
import { useCallback } from 'react';
import { ONE } from '@/constants/common';
import { addAlphaToHexString } from '../utils/convert/addAlphaToHexString';
export type GetHex3ColorGradientColorWithAlpha = (position: number) => string;
type UseTriColorLerpReturn = {
getHex3ColorGradientColorWithAlpha: GetHex3ColorGradientColorWithAlpha;
defaultColorHex: string;
};
export const useTriColorLerp = (): UseTriColorLerpReturn => {
const minColorValHexString = useAppSelector(minColorValSelector) || '';
const maxColorValHexString = useAppSelector(maxColorValSelector) || '';
const neutralColorValHexString = useAppSelector(neutralColorValSelector) || '';
const overlayOpacityValue = useAppSelector(overlayOpacitySelector) || '';
const overlayOpacityValue = useAppSelector(overlayOpacitySelector) || ONE;
const simpleColorValue = useAppSelector(simpleColorValSelector) || WHITE_HEX_OPACITY_0;
const getHex3ColorGradientColorWithAlpha = useCallback(
(position: number) =>
......@@ -32,5 +38,7 @@ export const useTriColorLerp = (): UseTriColorLerpReturn => {
[minColorValHexString, neutralColorValHexString, maxColorValHexString, overlayOpacityValue],
);
return { getHex3ColorGradientColorWithAlpha };
const defaultColorHex = addAlphaToHexString(simpleColorValue, Number(overlayOpacityValue));
return { getHex3ColorGradientColorWithAlpha, defaultColorHex };
};
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';
export const overlayBioEntityFixture = createFixture(z.array(overlayBioEntitySchema), {
seed: ZOD_SEED,
array: { min: 3, max: 3 },
});
......@@ -457,3 +457,21 @@ export const MODELS_MOCK_SHORT: MapModel[] = [
maxZoom: 5,
},
];
export const CORE_PD_MODEL_MOCK: MapModel = {
idObject: 5053,
width: 26779.25,
height: 13503.0,
defaultCenterX: null,
defaultCenterY: null,
description: '',
name: 'Core PD map',
defaultZoomLevel: null,
tileSize: 256,
references: [],
authors: [],
creationDate: null,
modificationDates: [],
minZoom: 2,
maxZoom: 9,
};
import { createSelector } from '@reduxjs/toolkit';
import { EMPTY_BACKGROUND_NAME } from '@/constants/backgrounds';
import { mapDataSelector } from '../map/map.selectors';
import { rootSelector } from '../root/root.selectors';
......@@ -36,3 +37,10 @@ export const currentBackgroundImagePathSelector = createSelector(
currentBackgroundImageSelector,
image => (image ? image.path : ''),
);
export const emptyBackgroundIdSelector = createSelector(backgroundsDataSelector, backgrounds => {
const emptyBackground = backgrounds?.find(
background => background.name === EMPTY_BACKGROUND_NAME,
);
return emptyBackground?.id;
});
......@@ -6,6 +6,7 @@ import {
MIN_COLOR_VAL_NAME_ID,
NEUTRAL_COLOR_VAL_NAME_ID,
OVERLAY_OPACITY_NAME_ID,
SIMPLE_COLOR_VAL_NAME_ID,
} from './configuration.constants';
const configurationSelector = createSelector(rootSelector, state => state.configuration);
......@@ -31,3 +32,8 @@ export const overlayOpacitySelector = createSelector(
configurationSelector,
state => configurationAdapterSelectors.selectById(state, OVERLAY_OPACITY_NAME_ID)?.value,
);
export const simpleColorValSelector = createSelector(
configurationSelector,
state => configurationAdapterSelectors.selectById(state, SIMPLE_COLOR_VAL_NAME_ID)?.value,
);
......@@ -18,6 +18,7 @@ export const parseOverlayBioEntityToOlRenderingFormat = (
height: entity.left.height,
value: entity.right.value,
overlayId,
color: entity.right.color,
});
}
return acc;
......
import { Color } from './models';
export type OverlayBioEntityRender = {
id: number;
modelId: number;
......@@ -9,4 +11,5 @@ export type OverlayBioEntityRender = {
height: number;
value: number | null;
overlayId: number;
color: Color | null;
};
......@@ -2,6 +2,7 @@ import { bioEntityContentSchema } from '@/models/bioEntityContentSchema';
import { bioEntityResponseSchema } from '@/models/bioEntityResponseSchema';
import { bioEntitySchema } from '@/models/bioEntitySchema';
import { chemicalSchema } from '@/models/chemicalSchema';
import { colorSchema } from '@/models/colorSchema';
import { configurationOptionSchema } from '@/models/configurationOptionSchema';
import { disease } from '@/models/disease';
import { drugSchema } from '@/models/drugSchema';
......@@ -39,3 +40,4 @@ export type ElementSearchResult = z.infer<typeof elementSearchResult>;
export type ElementSearchResultType = z.infer<typeof elementSearchResultType>;
export type ConfigurationOption = z.infer<typeof configurationOptionSchema>;
export type OverlayBioEntity = z.infer<typeof overlayBioEntitySchema>;
export type Color = z.infer<typeof colorSchema>;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment