Skip to content

Commit

Permalink
🔧 Merge statistics dashboard.
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasGilg committed Dec 11, 2024
1 parent d8c01ed commit 266c45d
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 96 deletions.
1 change: 0 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"@amcharts/amcharts5": "^5.8.4",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/base": "^5.0.0-beta.40",
"@mui/icons-material": "^5.15.0",
"@mui/lab": "^5.0.0-alpha.170",
"@mui/base": "^5.0.0-beta.40",
Expand Down
134 changes: 81 additions & 53 deletions frontend/src/components/InspireGridComponents/BaseLayer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2024 German Aerospace Center (DLR)
// SPDX-License-Identifier: Apache-2.0

import React, {useCallback, useContext, useEffect, useMemo} from 'react';
import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react';
import {LayerGroup, LayersControl, MapContainer, TileLayer, Rectangle, useMap, Polyline} from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import {getGridNew, getCellFromPosition} from './inspire';
Expand Down Expand Up @@ -31,8 +31,8 @@ function MapEventsHandler({setMapZoom, setMapBounds, setMapCenter}: BaseLayerPro

useEffect(() => {
const bounds: MapBounds = [
[52.248, 10.477],
[52.273, 10.572],
[52.15, 10.2],
[52.5, 10.8],
];
map.setMaxBounds(bounds);

Expand Down Expand Up @@ -73,9 +73,9 @@ function MapEventsHandler({setMapZoom, setMapBounds, setMapCenter}: BaseLayerPro
}

function getResolutionFromZoom(input: number): number {
const inputStart = 14;
const inputStart = 13;
const inputEnd = 18;
const outputStart = 9;
const outputStart = 8;
const outputEnd = 12;

if (input < inputStart || input > inputEnd) {
Expand All @@ -89,29 +89,25 @@ function getResolutionFromZoom(input: number): number {
export default function BaseLayer({
mapZoom = 14,
mapBounds = [
[52.248, 10.477],
[52.273, 10.572],
[52.15, 10],
[52.5, 11],
],
mapCenter = [52.26, 10.525],
inspireGrid = true,
inspireGridLevel = 10,
setMapZoom,
setMapBounds,
setMapCenter,
}: BaseLayerProps): JSX.Element {
const context = useContext(PandemosContext);
const selectedTab = useAppSelector((state) => state.userPreference.selectedSidebarTab ?? '1');
const filter = useAppSelector((state) => state.pandemosFilter);
const [showTrips, setShowTrips] = useState(false);
const [showHeatMap, setShowHeatMap] = useState(false);

const gridResolution = useMemo(() => getResolutionFromZoom(mapZoom), [mapZoom]);

const gridData = useMemo(() => {
const bounds: MapBounds = [
[52.248, 10.477],
[52.273, 10.572],
];
return getGridNew(mapBounds, gridResolution);
}, [mapBounds, gridResolution]);
}, [gridResolution, mapBounds]);

const getLocationPos = useCallback(
(location: number) => {
Expand All @@ -121,36 +117,34 @@ export default function BaseLayer({
[context.locations]
);

// Calculate infected locations from filtered trip chains
const infectedLocations = useMemo(() => {
if (!showHeatMap) {
return [];
}

const infectedLocations: {pos: number[]; infectionType: number}[] = [];
context.filteredTripChains?.forEach((tripChains) => {
tripChains.forEach((tripChainId) => {
if (context.tripChains) {
const tripChain = context.tripChains.get(tripChainId);
tripChain?.forEach((trip, index) => {
infectedLocations.push({
pos: getLocationPos(trip.start_location),
infectionType: trip.infection_state,
});
/*if (index > 0) {
if (
infectionStates.includes(trip.infection_state) &&
trip.infection_state !== tripChain[index - 1].infection_state &&
susceptibleStates.includes(tripChain[index - 1].infection_state)
) {
infectedLocations.push({
pos: getLocationPos(trip.start_location),
infectionType: trip.infection_state,
});
}
}*/
});
}
context.tripChains?.forEach((tripChain) => {
tripChain?.forEach((trip) => {
infectedLocations.push({
pos: getLocationPos(trip.start_location),
infectionType: trip.infection_state,
});
/*if (index > 0) {
if (
infectionStates.includes(trip.infection_state) &&
trip.infection_state !== tripChain[index - 1].infection_state &&
susceptibleStates.includes(tripChain[index - 1].infection_state)
) {
infectedLocations.push({
pos: getLocationPos(trip.start_location),
infectionType: trip.infection_state,
});
}
}*/
});
});
return infectedLocations;
}, [context.filteredTripChains, context.tripChains, getLocationPos]);
}, [context.tripChains, getLocationPos, showHeatMap]);
const getColorForInfection = (infectionCount: number, maxInfectionCount: number) => {
const ratio = infectionCount / maxInfectionCount;

Expand All @@ -164,11 +158,15 @@ export default function BaseLayer({

// Calculate infection count per grid cell
const infectedCellData = useMemo(() => {
if (!showHeatMap) {
return {cellsData: [], maxInfectionCount: 0};
}

const cellsData: {bounds: [[number, number], [number, number]]; infectionCount: number}[] = [];

gridData.rectangles.forEach(([latMin, lonMin, latMax, lonMax]) => {
const infectionCount = infectedLocations.filter((loc) => {
return loc.pos[0] >= latMin && loc.pos[0] <= latMax && loc.pos[1] >= lonMin && loc.pos[1] <= lonMax;
return loc.pos[1] >= latMin && loc.pos[1] <= latMax && loc.pos[0] >= lonMin && loc.pos[0] <= lonMax;
}).length;

cellsData.push({
Expand All @@ -183,10 +181,14 @@ export default function BaseLayer({
const maxInfectionCount = Math.max(...cellsData.map((cell) => cell.infectionCount), 0);

return {cellsData, maxInfectionCount};
}, [infectedLocations, gridData]);
}, [showHeatMap, gridData.rectangles, infectedLocations]);

const trips = useMemo(() => {
const trips: {pos: [number, number]; color: string}[] = [];
if (!showTrips) {
return [];
}

const trips: {id: number; pos: [number, number]; color: string}[] = [];

if (selectedTab === '3') {
context.filteredTripChains?.forEach((tripChains) => {
Expand Down Expand Up @@ -218,6 +220,7 @@ export default function BaseLayer({
color = 'red'; // Unknown
}
trips.push({
id: trip.trip_id,
pos: [getLocationPos(trip.start_location), getLocationPos(trip.end_location)],
color: color,
});
Expand Down Expand Up @@ -254,9 +257,8 @@ export default function BaseLayer({
filter.destinationTypes.includes(end?.location_type ?? -1)
) {
if (
!filter.infectionStates ||
filter.infectionStates.length === 0 ||
filter.infectionStates.includes(trip.infection_state)
((!filter.infectionStates || filter.infectionStates.length === 0) && trip.infection_state > 0) ||
filter.infectionStates?.includes(trip.infection_state)
) {
let color: string;
switch (trip.transport_mode) {
Expand All @@ -282,6 +284,7 @@ export default function BaseLayer({
color = 'red'; // Unknown
}
trips.push({
id: trip.trip_id,
pos: [getLocationPos(trip.start_location), getLocationPos(trip.end_location)],
color: color,
});
Expand Down Expand Up @@ -311,6 +314,7 @@ export default function BaseLayer({
filter.tripDurationMin,
getLocationPos,
selectedTab,
showTrips,
]);

return (
Expand All @@ -320,6 +324,7 @@ export default function BaseLayer({
scrollWheelZoom={true}
doubleClickZoom={false}
dragging={true}
minZoom={13}
maxBounds={mapBounds}
maxBoundsViscosity={1.0}
style={{height: '100%', zIndex: '1', position: 'relative'}}
Expand All @@ -328,9 +333,18 @@ export default function BaseLayer({
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/>
<LayersControl position='topright'>
<LayersControl.Overlay checked name='Infection Heatmap'>
<LayerGroup>
<LayersControl position='topright' sortLayers={false}>
<LayersControl.Overlay checked={showHeatMap} name='Infection Heatmap'>
<LayerGroup
eventHandlers={{
add() {
setShowHeatMap(true);
},
remove() {
setShowHeatMap(false);
},
}}
>
{infectedCellData.cellsData.map((rectangle, index) => {
const fillColor = getColorForInfection(rectangle.infectionCount, infectedCellData.maxInfectionCount);

Expand All @@ -349,11 +363,25 @@ export default function BaseLayer({
})}
</LayerGroup>
</LayersControl.Overlay>
<LayersControl.Overlay checked name='Trips'>
<LayerGroup>
{trips.map((line, index) => (
<Polyline key={index} pathOptions={{weight: 1, color: line.color}} positions={line.pos} zIndex={1000} />
))}
<LayersControl.Overlay checked={showTrips} name='Trips'>
<LayerGroup
eventHandlers={{
add() {
setShowTrips(true);
},
remove() {
setShowTrips(false);
},
}}
>
{trips
.map((line) => (
<Polyline
key={line.id}
pathOptions={{weight: 1, color: line.color}}
positions={line.pos.map((pos) => [pos[1], pos[0]])}
/>
))}
</LayerGroup>
</LayersControl.Overlay>
</LayersControl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
selectActivities,
selectAgeGroups,
selectDestinationTypes,
selectInfectionStates,
selectInfectionStates, selectOriginTypes,
selectTransportationModes,
selectTripDuration,
} from 'store/PandemosFilterSlice';
Expand Down Expand Up @@ -37,7 +37,6 @@ export default function StatisticsDashboard(props: any): JSX.Element {
// To reset individual chart
const resetChart = (chartId: string) => {
const chart = chartRefs.current[chartId]; // Retrieve the chart from chartRefs using chartId
console.log('chart', chart);
if (chart) {
chart.filterAll();
chart.redraw();
Expand All @@ -49,8 +48,6 @@ export default function StatisticsDashboard(props: any): JSX.Element {

useLayoutEffect(() => {
if (context.expandedTrips && context.expandedTrips?.size() > 0) {
console.log('+++++++', context.expandedTrips);

// **************************************** 1. INFECTION CHART ******************************************//

// Create the dimension based on infection_state
Expand Down Expand Up @@ -79,7 +76,7 @@ export default function StatisticsDashboard(props: any): JSX.Element {
.keyAccessor((d: any) => KeyInfo.infection_state[d.key].icon)
.colors(d3.scaleOrdinal(d3.schemeBlues[9].slice().reverse()))
.on('filtered', function (_chart: any) {
const selectedFilters = _chart.filters();
const selectedFilters = _chart.filters().map((d: any) => Object.values(KeyInfo.infection_state).findIndex((e: any) => e.icon === d));
dispatch(
selectInfectionStates({
infectionStates: selectedFilters,
Expand Down Expand Up @@ -136,9 +133,14 @@ export default function StatisticsDashboard(props: any): JSX.Element {

odInfectionChart.on('filtered', function (_chart: any, filter: any) {
const selectedFilters = _chart.filters();
dispatch(
selectOriginTypes({
originTypes: selectedFilters.map((d: any) => d[0]),
})
);
dispatch(
selectDestinationTypes({
destinationTypes: selectedFilters,
destinationTypes: selectedFilters.map((d: any) => d[1]),
})
);
});
Expand Down Expand Up @@ -231,7 +233,6 @@ export default function StatisticsDashboard(props: any): JSX.Element {
return Math.floor((endTime - startTime) / 60);
});
const tripDurationGroup = tripDurationDimension.group().reduceCount();
console.log('tripDurationxxxx', tripDurationGroup.top(3));
if (!tripDurationDimension || !tripDurationGroup) {
console.error('Trip duration dimension or group is not defined');
return;
Expand All @@ -248,11 +249,13 @@ export default function StatisticsDashboard(props: any): JSX.Element {
.clipPadding(10)
.group(tripDurationGroup)
.on('filtered', function (_chart: any, filter: any) {
/*const selectedFilters = _chart.filters();
dispatch(
selectTripDuration({
// tripDurationMax: filter // TODO: once the real data is available
start: Math.round(selectedFilters[0][0]),
end: Math.round(selectedFilters[0][1]),
})
);
);*/
})
.title((d: any) => d.value);

Expand All @@ -261,7 +264,6 @@ export default function StatisticsDashboard(props: any): JSX.Element {
// **************************************** 5. AGE CHART ******************************************//
const ageDimension = context.expandedTrips?.dimension((d) => d.agent_age_group);
const ageGroup = ageDimension?.group().reduceCount();
console.log('agebins', ageGroup.all());
if (!ageDimension || !ageGroup) {
console.error('Age dimension or group is not defined');
return;
Expand Down Expand Up @@ -302,7 +304,6 @@ export default function StatisticsDashboard(props: any): JSX.Element {
variant='contained'
startIcon={<RestartAltIcon />}
onClick={resetFilters}
href='javascript:dashboard.filterAll();dc.redrawAll();'
sx={{
width: '80px',
height: '26px',
Expand Down
28 changes: 3 additions & 25 deletions frontend/src/data_sockets/PandemosContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ export const PandemosProvider = ({children}: {children: React.ReactNode}) => {
return (
({
...trip,
agent_age_group: agents[trip.agent_id].age_group,
start_location_type: locations[trip.start_location].location_type,
end_location_type: locations[trip.end_location].location_type,
agent_age_group: agents.find((agent) => agent.agent_id === trip.agent_id)!.age_group,
start_location_type: locations.find((loc) => loc.location_id === trip.start_location)!.location_type,
end_location_type: locations.find((loc) => loc.location_id === trip.end_location)!.location_type,
} as TripExpanded) ?? {}
);
})
Expand All @@ -136,29 +136,7 @@ export const PandemosProvider = ({children}: {children: React.ReactNode}) => {
return crossfilter([]);
}
}, [agents, locations, trips]);
/*
// Preprocess trip chains
const tripChains = useMemo<Array<TripChain>>(() => {
const agentTrips = new Map<number, Array<Trip>>();
// Group trips by agent
for (const trip of trips ?? []) {
agentTrips.set(trip.agent_id, [...(agentTrips.get(trip.agent_id) ?? []), trip]);
}

let chain_id = 0;
const tripChains = new Array<TripChain>();
for (const tripChain of agentTrips.values()) {
let start = 0;
tripChain.forEach((trip, index) => {
if (locations![trip.start_location].location_type === 0) start = index;
if (trip.activity === 6)
tripChains.push({agent_id: trip.agent_id, chain_id: chain_id++, trips: tripChain.slice(start, index + 1)});
});
}
return tripChains;
}, [trips, locations]);
*/
return (
<PandemosContext.Provider
value={useMemo(
Expand Down
Loading

0 comments on commit 266c45d

Please sign in to comment.