Skip to content

Commit

Permalink
feat(components): gs-sequences-by-location: show how many sequences w…
Browse files Browse the repository at this point in the history
…ere matched on locations in map view

resolves #614
  • Loading branch information
fengelniederhammer committed Dec 17, 2024
1 parent 84a9165 commit 0e3c8bc
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 12 deletions.
103 changes: 93 additions & 10 deletions components/src/preact/map/sequences-by-location-map.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Feature, FeatureCollection, GeometryObject } from 'geojson';
import Leaflet, { type Layer, type LayerGroup } from 'leaflet';
import type { FunctionComponent } from 'preact';
import { useEffect, useRef } from 'preact/hooks';
import { useEffect, useMemo, useRef } from 'preact/hooks';

import { type GeoJsonFeatureProperties, type MapSource, useGeoJsonMap } from './useGeoJsonMap';
import { type AggregateData } from '../../query/queryAggregateData';
import { InfoHeadline1, InfoParagraph } from '../components/info';
import { LoadingDisplay } from '../components/loading-display';
import { formatProportion } from '../shared/table/formatProportion';

Expand Down Expand Up @@ -52,18 +53,27 @@ export const SequencesByLocationMapInner: FunctionComponent<SequencesByLocationM
}) => {
const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
if (!ref.current || geojsonData === undefined || locationData === undefined) {
return;
}

const { locations, totalCount, countOfMatchedLocationData, unmatchedLocations } = useMemo(() => {
const countAndProportionByCountry = buildLookupByLocationField(locationData, lapisLocationField);
const locations = matchLocationDataAndGeoJsonFeatures(
const { locations, unmatchedLocations } = matchLocationDataAndGeoJsonFeatures(
geojsonData,
countAndProportionByCountry,
lapisLocationField,
);

const totalCount = locationData.map((value) => value.count).reduce((sum, b) => sum + b, 0);
const countOfMatchedLocationData = locations
.map((location) => location.properties.data?.count ?? 0)
.reduce((sum, b) => sum + b, 0);

return { locations, totalCount, countOfMatchedLocationData, unmatchedLocations };
}, [geojsonData, locationData, lapisLocationField]);

useEffect(() => {
if (!ref.current) {
return;
}

const leafletMap = Leaflet.map(ref.current, {
scrollWheelZoom: enableMapNavigation,
zoomControl: enableMapNavigation,
Expand All @@ -88,9 +98,82 @@ export const SequencesByLocationMapInner: FunctionComponent<SequencesByLocationM
return () => {
leafletMap.remove();
};
}, [ref, locationData, geojsonData, enableMapNavigation, lapisLocationField, zoom, offsetX, offsetY]);
}, [ref, locations, enableMapNavigation, lapisLocationField, zoom, offsetX, offsetY]);

return <div ref={ref} className='h-full' />;
const nullCount = locationData.find((row) => row[lapisLocationField] === null)?.count ?? 0;

return (
<div className='h-full'>
<div ref={ref} className='h-full' />
<div className='relative'>
<DataMatchInformation
totalCount={totalCount}
countOfMatchedLocationData={countOfMatchedLocationData}
unmatchedLocations={unmatchedLocations}
nullCount={nullCount}
/>
</div>
</div>
);
};

type DataMatchInformationProps = {
totalCount: number;
countOfMatchedLocationData: number;
unmatchedLocations: string[];
nullCount: number;
};

const DataMatchInformation: FunctionComponent<DataMatchInformationProps> = ({
totalCount,
countOfMatchedLocationData,
unmatchedLocations,
nullCount,
}) => {
const dialogRef = useRef<HTMLDialogElement>(null);

const proportion = formatProportion(countOfMatchedLocationData / totalCount);

return (
<>
<button
onClick={() => dialogRef.current?.showModal()}
className='text-sm absolute bottom-0 px-1 z-[1001] bg-white rounded border cursor-pointer tooltip'
data-tip='Click for detailed information'
>
This map shows {proportion} of the data.
</button>
<dialog ref={dialogRef} className={'modal modal-middle'}>
<div className='modal-box sm:max-w-3xl'>
<InfoHeadline1>Sequences By Location - Map View</InfoHeadline1>
<InfoParagraph>
The current filter has matched {totalCount.toLocaleString('en-us')} sequences. We were able to
match {countOfMatchedLocationData.toLocaleString('en-us')} sequences ({proportion}) on locations
on the map.
</InfoParagraph>
{unmatchedLocations.length > 0 && (
<InfoParagraph>
The following locations from the data could not be matched on the map:{' '}
{unmatchedLocations.map((it) => `"${it}"`).join(', ')}
</InfoParagraph>
)}
{nullCount > 0 && (
<InfoParagraph>
{nullCount.toLocaleString('en-us')} matching sequences have no location information.
</InfoParagraph>
)}
<div className='modal-action'>
<form method='dialog'>
<button className={'float-right underline text-sm hover:text-blue-700 mr-2'}>Close</button>
</form>
</div>
</div>
<form method='dialog' className='modal-backdrop'>
<button>Helper to close when clicked outside</button>
</form>
</dialog>
</>
);
};

function buildLookupByLocationField(locationData: AggregateData, lapisLocationField: string) {
Expand Down Expand Up @@ -139,7 +222,7 @@ function matchLocationDataAndGeoJsonFeatures(
console.warn(unmatchedLocationsWarning); // eslint-disable-line no-console -- We should give some feedback about unmatched location data.
}

return locations;
return { locations, unmatchedLocations };
}

function getColor(value: number | undefined): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const Template: StoryObj<SequencesByLocationProps> = {
args: {
enableMapNavigation: false,
width: '1100px',
height: '800px',
height: '700px',
views: ['map', 'table'],
pageSize: 10,
},
Expand Down Expand Up @@ -150,7 +150,7 @@ export const Germany: StoryObj<SequencesByLocationProps> = {
topologyObjectsKey: 'deu',
},
views: ['map', 'table'],
zoom: 6.3,
zoom: 6,
offsetX: 10,
offsetY: 51.4,
},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0e3c8bc

Please sign in to comment.