Skip to content

Commit

Permalink
fix: dynamic submissions legend chloropeth (#1250)
Browse files Browse the repository at this point in the history
* fix projectSubmissions: dynamic mapLegend and taskChloropeth on increasing submissions count

* feat projectSubmissionsMap: popup showing expected/submissions count add

* fix homeSCSS: width fix

* fix popupHeader: border fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
NSUWAL123 and pre-commit-ci[bot] authored Feb 23, 2024
1 parent 28f0ac0 commit 98fcf58
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { renderToString } from 'react-dom/server';
import Overlay from 'ol/Overlay';
import { getCenter } from 'ol/extent';
import './asyncpopup.scss';

type asyncPopupPropType = {
map: any;
fetchPopupData?: any;
popupUI: any;
openPopupFor?: () => void;
zoomedExtent?: any;
onPopupClose?: () => void;
closePopup?: any;
loading?: boolean;
showOnHover?: string;
};

function hasKey(obj, key) {
return Object.keys(obj).some((item) => item === key);
}

const layerIds = ['code'];
const popupId = 'popupx';

const AsyncPopup = ({
map,
fetchPopupData,
popupUI,
openPopupFor,
zoomedExtent,
onPopupClose,
closePopup = false,
loading = false,
showOnHover = 'click',
}: asyncPopupPropType) => {
const popupRef = useRef<any>(null);
const popupCloserRef = useRef<any>(null);
const [coordinates, setCoordinates] = useState<any>(null);
const [overlay, setOverlay] = useState<any>(null);
const [properties, setProperties] = useState(null);
const [popupHTML, setPopupHTML] = useState<HTMLBodyElement | string>('');

// add overlay to popupRef
useEffect(() => {
if (!map || !popupRef.current) return;
const overlayInstance = new Overlay({
element: popupRef.current,
positioning: 'center-center',
id: popupId,
});
setOverlay(overlayInstance);
}, [map, popupRef]);

// function for closing popup
const closePopupFn = useCallback(() => {
if (!popupCloserRef.current || !overlay) return;
overlay.setPosition(undefined);
setPopupHTML('');
setProperties(null);
if (popupCloserRef?.current instanceof HTMLElement) {
popupCloserRef.current.blur();
}
}, [overlay, popupCloserRef]);

useEffect(() => {
if (!map || !closePopup) return;
closePopupFn();
}, [map, closePopup, closePopupFn]);

useEffect(() => {
/**
* Alert if clicked on outside of element
*/
function handleClickOutside(event) {
if (popupRef.current && !popupRef?.current?.contains(event.target)) {
// alert('You clicked outside of me!');
overlay.setPosition(undefined);
setPopupHTML('');
setProperties(null);
if (popupCloserRef?.current instanceof HTMLElement) {
popupCloserRef?.current.blur();
}
}
}
// Bind the event listener
document.addEventListener('mousedown', handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener('mousedown', handleClickOutside);
};
}, [overlay]);
// get properties and coordinates of feature
useEffect(() => {
if (!map) return;
if (!overlay) return;

map.on(showOnHover, (evt) => {
map.updateSize();
overlay.setPosition(undefined);
setPopupHTML('');
setProperties(null);
if (popupCloserRef?.current instanceof HTMLElement) {
popupCloserRef.current?.blur();
}
const { coordinate } = evt;
const features = map.getFeaturesAtPixel(evt.pixel);

if (features.length < 1) {
closePopupFn();
return;
}
const featureProperties = features[0].getProperties();
const { uid } = featureProperties;
if (layerIds.includes(uid) || (hasKey(featureProperties, 'uid') && featureProperties?.uid)) {
setProperties(featureProperties);
setCoordinates(coordinate);
} else {
closePopupFn();
setProperties(null);
setCoordinates(null);
}
});
}, [map, closePopupFn]);

// fetch popup data when properties is set
useEffect(() => {
if (!map || !properties) return;
const { layerId } = properties;
if (layerIds.includes(layerId) || hasKey(properties, 'layer')) {
fetchPopupData(properties);
}
// eslint-disable-next-line
}, [map, properties]);

useEffect(() => {
if (!map || !coordinates || !overlay || !properties || closePopup) return;
const htmlString = renderToString(popupUI(properties));
setPopupHTML(htmlString);

overlay.setPosition([coordinates[0], coordinates[1]]);
const popupOverlay = map.getOverlayById(popupId);
if (!popupOverlay) {
map.addOverlay(overlay);
}
}, [map, overlay, coordinates, popupUI, properties, closePopup]);

// useEffect(() => {
// if (!map || !openPopupFor || !zoomedExtent) return;
// const center = getCenter(zoomedExtent);
// setProperties({ id: openPopupFor, layer: 'site' });
// setCoordinates(center);
// }, [map, openPopupFor, zoomedExtent]);

return (
<div
ref={popupRef}
id="popup"
className="ol-popup"
style={{ zIndex: 100009 }}
onBlur={(e) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
// Not triggered when swapping focus between children
closePopupFn();
}
}}
>
<button
ref={popupCloserRef}
id="popup-closer"
className="ol-popup-closer"
type="button"
onClick={closePopupFn}
onKeyDown={closePopupFn}
/>
<div id="popup-content" dangerouslySetInnerHTML={{ __html: popupHTML }} />
</div>
);
};

export default AsyncPopup;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.ol-popup {
position: absolute;
background-color: white;
border-radius: 10px;
min-height: fit-content;
margin-top: 12px;
bottom: 12px;
left: -50px;
}
.ol-popup:after,
.ol-popup:before {
top: 100%;
border: solid transparent;
content: ' ';
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.ol-popup:after {
border-top-color: white;
border-width: 10px;
left: 48px;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 48px;
margin-left: -11px;
}
.ol-popup-closer {
display: none;
}

#popup-content {
height: fit-content;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { ProjectActions } from '@/store/slices/ProjectSlice';
import { basicGeojsonTemplate } from '@/utilities/mapUtils';
import TaskSubmissionsMapLegend from '@/components/ProjectSubmissions/TaskSubmissionsMapLegend';
import Accordion from '@/components/common/Accordion';
import AsyncPopup from '@/components/MapComponent/OpenLayersComponent/AsyncPopup/AsyncPopup';
import { taskFeaturePropertyType, taskInfoType } from '@/models/submission/submissionModel';
import { isValidUrl } from '@/utilfunctions/urlChecker';

export const defaultStyles = {
Expand Down Expand Up @@ -69,7 +71,7 @@ const colorCodes = {
};
function colorRange(data, noOfRange) {
if (data?.length === 0) return [];
const actualCodes = [{ min: 0, max: 0, color: '#605f5e' }];
const actualCodes = [{ min: 0, max: 0, color: '#FF4538' }];
const maxVal = Math.max(...data?.map((d) => d.count));
const maxValue = maxVal <= noOfRange ? 10 : maxVal;
// const minValue = Math.min(...data?.map((d) => d.count)) 0;
Expand Down Expand Up @@ -109,7 +111,7 @@ const TaskSubmissionsMap = () => {
const projectInfo = CoreModules.useAppSelector((state) => state.project.projectInfo);
const projectTaskBoundries = CoreModules.useAppSelector((state) => state.project.projectTaskBoundries);

const taskInfo = CoreModules.useAppSelector((state) => state.task.taskInfo);
const taskInfo: taskInfoType[] = CoreModules.useAppSelector((state) => state.task.taskInfo);
const federalWiseProjectCount = taskInfo?.map((task) => ({
code: task.task_id,
count: task.submission_count,
Expand Down Expand Up @@ -184,7 +186,7 @@ const TaskSubmissionsMap = () => {
(style, feature, resolution) => {
const stylex = { ...style };
stylex.fillOpacity = 80;
const getFederal = federalWiseProjectCount?.find((d) => d.code === feature.getProperties().uid);
const getFederal = federalWiseProjectCount?.find((d) => d.code == feature.getProperties().uid);
const getFederalCount = getFederal?.count;
stylex.labelMaxResolution = 1000;
stylex.showLabel = true;
Expand All @@ -207,6 +209,26 @@ const TaskSubmissionsMap = () => {
map?.on('loadend', function () {
map.getTargetElement().classList.remove('spinner');
});

const taskSubmissionsPopupUI = (properties: taskFeaturePropertyType) => {
const currentTask = taskInfo?.filter((task) => +task.task_id === properties.uid);
return (
<div className="fmtm-h-fit">
<h2 className="fmtm-border-b-[2px] fmtm-border-primaryRed fmtm-w-fit fmtm-pr-1">
Task ID: #{currentTask?.[0].task_id}
</h2>
<div className="fmtm-flex fmtm-flex-col fmtm-gap-1 fmtm-mt-1">
<p>
Expected Count: <span className="fmtm-text-primaryRed">{currentTask?.[0].feature_count}</span>
</p>
<p>
Submission Count: <span className="fmtm-text-primaryRed">{currentTask?.[0].submission_count}</span>
</p>
</div>
</div>
);
};

return (
<CoreModules.Box
sx={{
Expand Down Expand Up @@ -246,7 +268,7 @@ const TaskSubmissionsMap = () => {
)}
<div className="fmtm-absolute fmtm-bottom-2 fmtm-left-2 sm:fmtm-bottom-5 sm:fmtm-left-5 fmtm-z-50 fmtm-rounded-lg">
<Accordion
body={<TaskSubmissionsMapLegend defaultTheme={defaultTheme} />}
body={<TaskSubmissionsMapLegend legendColorArray={legendColorArray} />}
header={
<p className="fmtm-text-lg fmtm-font-normal fmtm-my-auto fmtm-mb-[0.35rem] fmtm-ml-2">
No. of Submissions
Expand All @@ -257,6 +279,7 @@ const TaskSubmissionsMap = () => {
collapsed={true}
/>
</div>
<AsyncPopup map={map} popupUI={taskSubmissionsPopupUI} />
{dataExtractUrl && isValidUrl(dataExtractUrl) && (
<VectorLayer fgbUrl={dataExtractUrl} fgbExtent={dataExtractExtent} zIndex={15} />
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import React from 'react';

const colorCodes = [
{ color: '#0062AC', min: 130, max: 160 },
{ color: '#4A90D9', min: 100, max: 130 },
{ color: '#7CB2E8', min: 50, max: 100 },
{ color: '#A9D2F3', min: 10, max: 50 },
{ color: '#FF4538', min: 0, max: 0 },
];

type legendColorArrayType = {
color: string;
min: number;
max: number;
};
const LegendListItem = ({ code }) => (
<div className="fmtm-flex fmtm-items-center fmtm-gap-2">
<div style={{ backgroundColor: code.color }} className="fmtm-w-10 fmtm-h-6 fmtm-mx-2"></div>
Expand All @@ -23,13 +20,11 @@ const LegendListItem = ({ code }) => (
</div>
);

const TaskSubmissionsMapLegend = () => {
const TaskSubmissionsMapLegend = ({ legendColorArray }: { legendColorArray: legendColorArrayType[] }) => {
return (
<div className="fmtm-py-3">
<div className="fmtm-flex fmtm-flex-col fmtm-gap-2 sm:fmtm-gap-4">
{colorCodes.map((code) => (
<LegendListItem key={code.color} code={code} />
))}
{legendColorArray?.reverse()?.map((code) => <LegendListItem key={code.color} code={code} />)}
</div>
</div>
);
Expand Down
7 changes: 7 additions & 0 deletions src/frontend/src/models/submission/submissionModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ export type taskInfoType = {
last_submission: string | null;
feature_count: number;
};

export type taskFeaturePropertyType = {
fid: number;
geometry: any;
name: string | null;
uid: number;
};
2 changes: 1 addition & 1 deletion src/frontend/src/styles/home.scss
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
border: 1px solid #cccccc;
bottom: 12px;
left: -50px;
min-width: 280px;
min-width: 180px;
}
.ol-popup:after,
.ol-popup:before {
Expand Down

0 comments on commit 98fcf58

Please sign in to comment.