Skip to content

Commit

Permalink
Feature/253 update charts multiple comparison (#314)
Browse files Browse the repository at this point in the history
* Update temporal API

* Fix lint

* Add multiple temporal comparison period

* Remove unused code

* remove unused code

* Update tenporal charts to show multiple comparison years

* Remove unused code

* Fix lint

* Fix lint
  • Loading branch information
zamuzakki authored Feb 6, 2025
1 parent 1dec9a0 commit b49f7c2
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 95 deletions.
76 changes: 70 additions & 6 deletions django_project/frontend/api_views/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
.. note:: Analysis APIs
"""
import uuid
from copy import deepcopy
from concurrent.futures import ThreadPoolExecutor
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
Expand Down Expand Up @@ -54,13 +55,76 @@ def run_baseline_analysis(self, data):
analysis_dict=analysis_dict
)

def _combine_temporal_analysis_results(self, years, input_results):
def merge_and_sort(arrays):
unique_dict = {}

for array in arrays:
for item in array['features']:
key = (
f"{item['properties']['Name']}-"
f"{item['properties']['date']}"
)
# Overwrites duplicates, ensuring uniqueness
unique_dict[key] = item
return list(unique_dict.values())

def add_empty_records(existing_records):
new_records = {}
for year in years:
has_record = len(
list(
filter(
lambda x: x['properties']['year'] == year,
existing_records
)
)
) > 0
if not has_record:
for record in existing_records:
key = f'{record["properties"]["Name"]}-{year}'
if key not in new_records:
new_record = deepcopy(record)
new_record['properties']['year'] = year
new_record['properties']['Bare ground'] = None
new_record['properties']['EVI'] = None
new_record['properties']['NDVI'] = None
new_records[key] = new_record
return new_records.values()

output_results = []
output_results.append(input_results[0][0])
output_results.append(input_results[0][1])
output_results[0]['features'] = merge_and_sort(
[ir[0] for ir in input_results]
)

output_results[0]['features'].extend(
add_empty_records(output_results[1]['features'])
)
# add empty result if no data exist for certain year
output_results[1]['features'] = merge_and_sort(
[ir[1] for ir in input_results]
)

output_results[0]['features'] = sorted(
output_results[0]['features'],
key=lambda x: x['properties']['date']
)
output_results[1]['features'] = sorted(
output_results[1]['features'],
key=lambda x: x['properties']['date']
)

return output_results

def run_temporal_analysis(self, data):
"""Run the temporal analysis."""
analysis_dict_list = []
comp_years = data['comparisonPeriod']['years'].split(',')
comp_quarters = data['comparisonPeriod'].get('quarters', '').split(',')
comp_years = data['comparisonPeriod']['year']
comp_quarters = data['comparisonPeriod'].get('quarter', [])
if len(comp_years) == 0:
comp_quarters = [''] * len(comp_years)
comp_quarters = [None] * len(comp_years)

analysis_dict_list = []
for idx, comp_year in enumerate(comp_years):
Expand Down Expand Up @@ -98,15 +162,15 @@ def run_temporal_analysis(self, data):
futures = [
executor.submit(
run_analysis,
analysis_dict,
data['latitude'],
data['longitude'],
data['latitude']
analysis_dict
) for analysis_dict in analysis_dict_list
]

# Collect results as they complete
results = [future.result() for future in futures]

results = self._combine_temporal_analysis_results(comp_years, results)
return results

def run_spatial_analysis(self, data):
Expand Down
4 changes: 2 additions & 2 deletions django_project/frontend/src/components/Map/DataTypes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
*/

export interface AnalysisDataPeriod {
year?: number;
quarter?: number;
year?: number | number[];
quarter?: number | number[];
}

export interface AnalysisData {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,83 @@
import React from 'react';
import React, { useState } from 'react';
import {
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
Box,
Select
Select,
Button,
} from "@chakra-ui/react";
import { AnalysisDataPeriod } from "../../DataTypes";


interface Props {
title: string;
value: AnalysisDataPeriod;
isQuarter: boolean;
onSelectedYear: (year: number) => void;
onSelectedQuarter: (quarter: number) => void;
multiple?: boolean;
onSelectedYear: (year: number | number[]) => void;
onSelectedQuarter: (quarter: number | number[]) => void;
}

type DefaultPanel = {
id: number,
year: number,
quarter: number | null
}

/** Reference period. */
export default function AnalysisReferencePeriod(
{ title, value, isQuarter, onSelectedYear, onSelectedQuarter }: Props
{ title, value, isQuarter, multiple=false, onSelectedYear, onSelectedQuarter }: Props
) {
const nowYear = new Date().getFullYear()
const years: number[] = []
const nowYear = new Date().getFullYear();
const years: number[] = [];
for (let i = 0; i <= 10; i++) {
years.push(nowYear - i)
years.push(nowYear - i);
}
years.reverse();

// State to manage multiple AccordionPanels

let defaultPanels: DefaultPanel[] = [];
if (multiple && Array.isArray(value.year) && Array.isArray(value.quarter)) {
for (let i = 0; i < value.year.length; i++) {
defaultPanels.push({ id: i, year: value.year[i], quarter: value.quarter[i] });
}
} else if (!Array.isArray(value.year) && !Array.isArray(value.quarter)){
defaultPanels = [{ id: 0, year: value.year, quarter: value.quarter }]
}
years.reverse()
const [panels, setPanels] = useState<DefaultPanel[]>(defaultPanels);

// Add a new AccordionPanel
const handleAddPanel = () => {
setPanels([...panels, { id: panels.length + 1, year: null, quarter: null }]);
};

// Remove an AccordionPanel
const handleDeletePanel = (id: number) => {
const updatedPanels = panels.filter(panel => panel.id !== id);
setPanels(updatedPanels);
onSelectedYear(updatedPanels.map(panel => panel.year)); // Update the parent component
onSelectedQuarter(updatedPanels.map(panel => panel.quarter)); // Update the parent component
};

// Handle year selection
const handleYearChange = (id: number, year: number) => {
const updatedPanels = panels.map(panel =>
panel.id === id ? { ...panel, year } : panel
);
setPanels(updatedPanels);
onSelectedYear(multiple ? updatedPanels.map(panel => panel.year) : updatedPanels[0].year); // Update the parent component
};

// Handle quarter selection
const handleQuarterChange = (id: number, quarter: number) => {
const updatedPanels = panels.map(panel =>
panel.id === id ? { ...panel, quarter } : panel
);
setPanels(updatedPanels);
onSelectedQuarter(multiple ? updatedPanels.map(panel => panel.quarter) : updatedPanels[0].quarter); // Update the parent component
};

return (
<AccordionItem>
Expand All @@ -36,49 +86,55 @@ export default function AnalysisReferencePeriod(
<Box flex="1" textAlign="left" fontWeight='bold' fontSize='13px'>
{title}
</Box>
<AccordionIcon/>
<AccordionIcon />
</AccordionButton>
</h2>
<AccordionPanel
pb={4}
pl={8}
fontSize='13px'
>
<Select
fontSize='13px'
height='2rem'
placeholder="Select a year"
value={value?.year ? value?.year : ''}
onChange={
evt => onSelectedYear(parseInt(evt.target.value))
}
>
{
years.map(year => {
return <option key={year} value={year}>{year}</option>
})
}
</Select>
{
isQuarter &&
{panels.map((panel) => (
<AccordionPanel key={panel.id} pb={4} pl={8} fontSize='13px'>
<Select
fontSize='13px'
height='2rem'
placeholder="Select a quarter"
value={value?.quarter ? value?.quarter : ''}
onChange={
evt => onSelectedQuarter(parseInt(evt.target.value))
}
placeholder="Select a year"
value={panel.year || ''}
onChange={(evt) => handleYearChange(panel.id, parseInt(evt.target.value))}
>
{
[1, 2, 3, 4].map(quarter => {
return <option key={quarter} value={quarter}>{quarter}</option>
})
}
{years.map(year => (
<option key={year} value={year}>{year}</option>
))}
</Select>
}
</AccordionPanel>
{isQuarter && (
<Select
fontSize='13px'
height='2rem'
placeholder="Select a quarter"
value={panel.quarter || ''}
onChange={(evt) => handleQuarterChange(panel.id, parseInt(evt.target.value))}
>
{[1, 2, 3, 4].map(quarter => (
<option key={quarter} value={quarter}>{quarter}</option>
))}
</Select>
)}
{
panels.length > 1 && <Button
size="sm"
colorScheme="red"
mt={2}
onClick={() => handleDeletePanel(panel.id)}
>
Delete
</Button>}
</AccordionPanel>
))}
{multiple && <AccordionPanel pb={4} pl={8}>
<Button
size="sm"
colorScheme="blue"
onClick={handleAddPanel}
>
Add more
</Button>
</AccordionPanel>}
</AccordionItem>
)
}

);
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,14 @@ export default function Analysis({ landscapes, layers, onLayerChecked, onLayerUn
// remove polygon for reference layer diff
geometrySelectorRef?.current?.removeLayer();
}
dispatch(doAnalysis(data))
const newData = {
...data,
comparisonPeriod: {
year: data.comparisonPeriod?.year,
quarter: data.temporalResolution == 'Quarterly' ? data.comparisonPeriod?.quarter : []
},
}
dispatch(doAnalysis(newData))
}

useEffect(() => {
Expand Down Expand Up @@ -209,6 +216,7 @@ export default function Analysis({ landscapes, layers, onLayerChecked, onLayerUn
disableSubmit = true;
}


return (
<Box fontSize='13px'>
<Accordion allowMultiple defaultIndex={[0, 1, 2, 3, 4, 5]}>
Expand Down Expand Up @@ -419,6 +427,7 @@ export default function Analysis({ landscapes, layers, onLayerChecked, onLayerUn
title='6) Select comparison period'
value={data.comparisonPeriod}
isQuarter={data.temporalResolution === TemporalResolution.QUARTERLY}
multiple={true}
onSelectedYear={(value: number) => setData({
...data,
comparisonPeriod: {
Expand Down
Loading

0 comments on commit b49f7c2

Please sign in to comment.